Merge "Fix size calculation" into sc-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 284e807..23dc720 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -123,13 +123,19 @@
     },
     dists: [
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/public/api",
             dest: "android-non-updatable.txt",
             tag: ".api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/public/api",
             dest: "android-non-updatable-removed.txt",
             tag: ".removed-api.txt",
@@ -137,21 +143,18 @@
     ],
 }
 
-priv_apps =
-    " --show-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+priv_apps = " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
     "\\)"
 
-priv_apps_in_stubs =
-    " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+priv_apps_in_stubs = " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
     "\\)"
 
 test = " --show-annotation android.annotation.TestApi"
 
-module_libs =
-    " --show-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
+module_libs = " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
     "\\)"
 
 droidstubs {
@@ -166,7 +169,7 @@
         last_released: {
             api_file: ":android-non-updatable.api.system.latest",
             removed_api_file: ":android-non-updatable-removed.api.system.latest",
-            baseline_file: ":android-non-updatable-incompatibilities.api.system.latest"
+            baseline_file: ":android-non-updatable-incompatibilities.api.system.latest",
         },
         api_lint: {
             enabled: true,
@@ -176,13 +179,19 @@
     },
     dists: [
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/system/api",
             dest: "android-non-updatable.txt",
             tag: ".api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/system/api",
             dest: "android-non-updatable-removed.txt",
             tag: ".removed-api.txt",
@@ -206,25 +215,37 @@
     },
     dists: [
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/test/api",
             dest: "android.txt",
             tag: ".api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/test/api",
             dest: "removed.txt",
             tag: ".removed-api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/test/api",
             dest: "android-non-updatable.txt",
             tag: ".api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/test/api",
             dest: "android-non-updatable-removed.txt",
             tag: ".removed-api.txt",
@@ -252,13 +273,19 @@
     },
     dists: [
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/module-lib/api",
             dest: "android-non-updatable.txt",
             tag: ".api.txt",
         },
         {
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             dir: "apistubs/android/module-lib/api",
             dest: "android-non-updatable-removed.txt",
             tag: ".removed-api.txt",
@@ -318,10 +345,13 @@
     java_version: "1.8",
     compile_dex: true,
     dist: {
-        targets: ["sdk", "win_sdk"],
+        targets: [
+            "sdk",
+            "win_sdk",
+        ],
         tag: ".jar",
         dest: "android-non-updatable.jar",
-    }
+    },
 }
 
 java_library_static {
@@ -337,7 +367,7 @@
 java_library_static {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults_stubs_current"],
-    srcs: [ ":system-api-stubs-docs-non-updatable" ],
+    srcs: [":system-api-stubs-docs-non-updatable"],
     libs: modules_system_stubs,
     dist: {
         dir: "apistubs/android/system",
@@ -380,7 +410,10 @@
 java_defaults {
     name: "android_stubs_dists_default",
     dist: {
-        targets: ["sdk", "win_sdk"],
+        targets: [
+            "sdk",
+            "win_sdk",
+        ],
         tag: ".jar",
         dest: "android.jar",
     },
@@ -411,7 +444,10 @@
     dists: [
         {
             // Legacy dist path
-            targets: ["sdk", "win_sdk"],
+            targets: [
+                "sdk",
+                "win_sdk",
+            ],
             tag: ".jar",
             dest: "android_system.jar",
         },
@@ -433,14 +469,6 @@
     dist: {
         dir: "apistubs/android/test",
     },
-    dists: [
-        {
-            // Legacy dist path
-            targets: ["sdk", "win_sdk"],
-            tag: ".jar",
-            dest: "android_test.jar",
-        },
-    ],
 }
 
 java_library_static {
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/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index 113f8fe..6dbbcb5 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -26,8 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Base64;
+import android.util.IndentingPrintWriter;
 
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
index ca588c5..09260b7 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
@@ -37,9 +37,9 @@
 import android.util.ArraySet;
 import android.util.Base64;
 import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 8b12beb..e477156 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.blob;
 
+import static android.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS;
 import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
 import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
@@ -36,6 +37,7 @@
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
 import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
 import static com.android.server.blob.BlobStoreUtils.getPackageResources;
@@ -45,15 +47,18 @@
 import android.app.blob.BlobHandle;
 import android.app.blob.LeaseInfo;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.ResourceId;
 import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
 import android.os.RevocableFileDescriptor;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.StatsEvent;
@@ -62,7 +67,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 import com.android.server.blob.BlobStoreManagerService.DumpArgs;
 
@@ -85,7 +89,6 @@
 
     private final long mBlobId;
     private final BlobHandle mBlobHandle;
-    private final int mUserId;
 
     @GuardedBy("mMetadataLock")
     private final ArraySet<Committer> mCommitters = new ArraySet<>();
@@ -94,24 +97,23 @@
     private final ArraySet<Leasee> mLeasees = new ArraySet<>();
 
     /**
-     * Contains packageName -> {RevocableFileDescriptors}.
+     * Contains Accessor -> {RevocableFileDescriptors}.
      *
      * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
      * that when clients access is revoked or the blob gets deleted, we can be sure that clients
      * do not have any reference to the blob and the space occupied by the blob can be freed.
      */
     @GuardedBy("mRevocableFds")
-    private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
+    private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds =
             new ArrayMap<>();
 
     // Do not access this directly, instead use #getBlobFile().
     private File mBlobFile;
 
-    BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
+    BlobMetadata(Context context, long blobId, BlobHandle blobHandle) {
         mContext = context;
         this.mBlobId = blobId;
         this.mBlobHandle = blobHandle;
-        this.mUserId = userId;
     }
 
     long getBlobId() {
@@ -122,10 +124,6 @@
         return mBlobHandle;
     }
 
-    int getUserId() {
-        return mUserId;
-    }
-
     void addOrReplaceCommitter(@NonNull Committer committer) {
         synchronized (mMetadataLock) {
             // We need to override the committer data, so first remove any existing
@@ -155,13 +153,24 @@
         }
     }
 
-    void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) {
+    void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
         synchronized (mMetadataLock) {
-            mCommitters.removeIf(committer ->
-                    !committer.packageName.equals(knownPackages.get(committer.uid)));
+            mCommitters.removeIf(committer -> {
+                final int userId = UserHandle.getUserId(committer.uid);
+                final SparseArray<String> userPackages = knownPackages.get(userId);
+                if (userPackages == null) {
+                    return true;
+                }
+                return !committer.packageName.equals(userPackages.get(committer.uid));
+            });
         }
     }
 
+    void addCommittersAndLeasees(BlobMetadata blobMetadata) {
+        mCommitters.addAll(blobMetadata.mCommitters);
+        mLeasees.addAll(blobMetadata.mLeasees);
+    }
+
     @Nullable
     Committer getExistingCommitter(@NonNull String packageName, int uid) {
         synchronized (mCommitters) {
@@ -201,10 +210,16 @@
         }
     }
 
-    void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) {
+    void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
         synchronized (mMetadataLock) {
-            mLeasees.removeIf(leasee ->
-                    !leasee.packageName.equals(knownPackages.get(leasee.uid)));
+            mLeasees.removeIf(leasee -> {
+                final int userId = UserHandle.getUserId(leasee.uid);
+                final SparseArray<String> userPackages = knownPackages.get(userId);
+                if (userPackages == null) {
+                    return true;
+                }
+                return !leasee.packageName.equals(userPackages.get(leasee.uid));
+            });
         }
     }
 
@@ -214,6 +229,25 @@
         }
     }
 
+    void removeDataForUser(int userId) {
+        synchronized (mMetadataLock) {
+            mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid)));
+            mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid)));
+            mRevocableFds.entrySet().removeIf(entry -> {
+                final Accessor accessor = entry.getKey();
+                final ArraySet<RevocableFileDescriptor> rFds = entry.getValue();
+                if (userId != UserHandle.getUserId(accessor.uid)) {
+                    return false;
+                }
+                for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) {
+                    rFds.valueAt(i).revoke();
+                }
+                rFds.clear();
+                return true;
+            });
+        }
+    }
+
     boolean hasValidLeases() {
         synchronized (mMetadataLock) {
             for (int i = 0, size = mLeasees.size(); i < size; ++i) {
@@ -244,8 +278,12 @@
                 }
             }
 
+            final int callingUserId = UserHandle.getUserId(callingUid);
             for (int i = 0, size = mCommitters.size(); i < size; ++i) {
                 final Committer committer = mCommitters.valueAt(i);
+                if (callingUserId != UserHandle.getUserId(committer.uid)) {
+                    continue;
+                }
 
                 // Check if the caller is the same package that committed the blob.
                 if (committer.equals(callingPackage, callingUid)) {
@@ -259,38 +297,105 @@
                     return true;
                 }
             }
+
+            final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers(
+                    callingPackage, callingUserId);
+            if (!canCallerAccessBlobsAcrossUsers) {
+                return false;
+            }
+            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+                final Committer committer = mCommitters.valueAt(i);
+                final int committerUserId = UserHandle.getUserId(committer.uid);
+                if (callingUserId == committerUserId) {
+                    continue;
+                }
+                if (!checkCallerCanAccessBlobsAcrossUsers(callingPackage, committerUserId)) {
+                    continue;
+                }
+
+                // Check if the caller is allowed access as per the access mode specified
+                // by the committer.
+                if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
+                        callingPackage, committer.packageName, callingUid, attributionTag)) {
+                    return true;
+                }
+            }
+
+        }
+        return false;
+    }
+
+    private static boolean checkCallerCanAccessBlobsAcrossUsers(
+            String callingPackage, int callingUserId) {
+        return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS,
+                callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    boolean hasACommitterOrLeaseeInUser(int userId) {
+        return hasACommitterInUser(userId) || hasALeaseeInUser(userId);
+    }
+
+    boolean hasACommitterInUser(int userId) {
+        synchronized (mMetadataLock) {
+            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+                final Committer committer = mCommitters.valueAt(i);
+                if (userId == UserHandle.getUserId(committer.uid)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasALeaseeInUser(int userId) {
+        synchronized (mMetadataLock) {
+            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+                final Leasee leasee = mLeasees.valueAt(i);
+                if (userId == UserHandle.getUserId(leasee.uid)) {
+                    return true;
+                }
+            }
         }
         return false;
     }
 
     boolean isACommitter(@NonNull String packageName, int uid) {
         synchronized (mMetadataLock) {
-            return isAnAccessor(mCommitters, packageName, uid);
+            return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid));
         }
     }
 
     boolean isALeasee(@Nullable String packageName, int uid) {
         synchronized (mMetadataLock) {
-            final Leasee leasee = getAccessor(mLeasees, packageName, uid);
+            final Leasee leasee = getAccessor(mLeasees, packageName, uid,
+                    UserHandle.getUserId(uid));
+            return leasee != null && leasee.isStillValid();
+        }
+    }
+
+    private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) {
+        synchronized (mMetadataLock) {
+            final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId);
             return leasee != null && leasee.isStillValid();
         }
     }
 
     private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
-            @Nullable String packageName, int uid) {
+            @Nullable String packageName, int uid, int userId) {
         // Check if the package is an accessor of the data blob.
-        return getAccessor(accessors, packageName, uid) != null;
+        return getAccessor(accessors, packageName, uid, userId) != null;
     }
 
     private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors,
-            @Nullable String packageName, int uid) {
+            @Nullable String packageName, int uid, int userId) {
         // Check if the package is an accessor of the data blob.
         for (int i = 0, size = accessors.size(); i < size; ++i) {
             final Accessor accessor = accessors.valueAt(i);
             if (packageName != null && uid != INVALID_UID
                     && accessor.equals(packageName, uid)) {
                 return (T) accessor;
-            } else if (packageName != null && accessor.packageName.equals(packageName)) {
+            } else if (packageName != null && accessor.packageName.equals(packageName)
+                    && userId == UserHandle.getUserId(accessor.uid)) {
                 return (T) accessor;
             } else if (uid != INVALID_UID && accessor.uid == uid) {
                 return (T) accessor;
@@ -299,23 +404,29 @@
         return null;
     }
 
-    boolean isALeasee(@NonNull String packageName) {
-        return isALeasee(packageName, INVALID_UID);
+    boolean shouldAttributeToLeasee(@NonNull String packageName, int userId,
+            boolean callerHasStatsPermission) {
+        if (!isALeaseeInUser(packageName, INVALID_UID, userId)) {
+            return false;
+        }
+        if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) {
+            return true;
+        }
+        return false;
     }
 
-    boolean isALeasee(int uid) {
-        return isALeasee(null, uid);
+    boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) {
+        final int userId = UserHandle.getUserId(uid);
+        if (!isALeaseeInUser(null, uid, userId)) {
+            return false;
+        }
+        if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) {
+            return true;
+        }
+        return false;
     }
 
-    boolean hasOtherLeasees(@NonNull String packageName) {
-        return hasOtherLeasees(packageName, INVALID_UID);
-    }
-
-    boolean hasOtherLeasees(int uid) {
-        return hasOtherLeasees(null, uid);
-    }
-
-    private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
+    private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) {
         synchronized (mMetadataLock) {
             for (int i = 0, size = mLeasees.size(); i < size; ++i) {
                 final Leasee leasee = mLeasees.valueAt(i);
@@ -326,7 +437,8 @@
                 if (packageName != null && uid != INVALID_UID
                         && !leasee.equals(packageName, uid)) {
                     return true;
-                } else if (packageName != null && !leasee.packageName.equals(packageName)) {
+                } else if (packageName != null && (!leasee.packageName.equals(packageName)
+                        || userId != UserHandle.getUserId(leasee.uid))) {
                     return true;
                 } else if (uid != INVALID_UID && leasee.uid != uid) {
                     return true;
@@ -371,7 +483,7 @@
         return mBlobFile;
     }
 
-    ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
+    ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException {
         // TODO: Add limit on opened fds
         FileDescriptor fd;
         try {
@@ -381,7 +493,7 @@
         }
         try {
             if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
-                return createRevocableFd(fd, callingPackage);
+                return createRevocableFd(fd, callingPackage, callingUid);
             } else {
                 return new ParcelFileDescriptor(fd);
             }
@@ -393,26 +505,28 @@
 
     @NonNull
     private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
-            String callingPackage) throws IOException {
+            String callingPackage, int callingUid) throws IOException {
         final RevocableFileDescriptor revocableFd =
                 new RevocableFileDescriptor(mContext, fd);
+        final Accessor accessor;
         synchronized (mRevocableFds) {
-            ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
-                    mRevocableFds.get(callingPackage);
-            if (revocableFdsForPkg == null) {
-                revocableFdsForPkg = new ArraySet<>();
-                mRevocableFds.put(callingPackage, revocableFdsForPkg);
+            accessor = new Accessor(callingPackage, callingUid);
+            ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
+                    mRevocableFds.get(accessor);
+            if (revocableFdsForAccessor == null) {
+                revocableFdsForAccessor = new ArraySet<>();
+                mRevocableFds.put(accessor, revocableFdsForAccessor);
             }
-            revocableFdsForPkg.add(revocableFd);
+            revocableFdsForAccessor.add(revocableFd);
         }
         revocableFd.addOnCloseListener((e) -> {
             synchronized (mRevocableFds) {
-                final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
-                        mRevocableFds.get(callingPackage);
-                if (revocableFdsForPkg != null) {
-                    revocableFdsForPkg.remove(revocableFd);
-                    if (revocableFdsForPkg.isEmpty()) {
-                        mRevocableFds.remove(callingPackage);
+                final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
+                        mRevocableFds.get(accessor);
+                if (revocableFdsForAccessor != null) {
+                    revocableFdsForAccessor.remove(revocableFd);
+                    if (revocableFdsForAccessor.isEmpty()) {
+                        mRevocableFds.remove(accessor);
                     }
                 }
             }
@@ -421,22 +535,23 @@
     }
 
     void destroy() {
-        revokeAllFds();
+        revokeAndClearAllFds();
         getBlobFile().delete();
     }
 
-    private void revokeAllFds() {
+    private void revokeAndClearAllFds() {
         synchronized (mRevocableFds) {
-            for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
-                final ArraySet<RevocableFileDescriptor> packageFds =
+            for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) {
+                final ArraySet<RevocableFileDescriptor> rFds =
                         mRevocableFds.valueAt(i);
-                if (packageFds == null) {
+                if (rFds == null) {
                     continue;
                 }
-                for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
-                    packageFds.valueAt(j).revoke();
+                for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) {
+                    rFds.valueAt(j).revoke();
                 }
             }
+            mRevocableFds.clear();
         }
     }
 
@@ -547,10 +662,10 @@
                 fout.println("<empty>");
             } else {
                 for (int i = 0, count = mRevocableFds.size(); i < count; ++i) {
-                    final String packageName = mRevocableFds.keyAt(i);
-                    final ArraySet<RevocableFileDescriptor> packageFds =
+                    final Accessor accessor = mRevocableFds.keyAt(i);
+                    final ArraySet<RevocableFileDescriptor> rFds =
                             mRevocableFds.valueAt(i);
-                    fout.println(packageName + "#" + packageFds.size());
+                    fout.println(accessor + ": #" + rFds.size());
                 }
             }
             fout.decreaseIndent();
@@ -560,7 +675,6 @@
     void writeToXml(XmlSerializer out) throws IOException {
         synchronized (mMetadataLock) {
             XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
-            XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);
 
             out.startTag(null, TAG_BLOB_HANDLE);
             mBlobHandle.writeToXml(out);
@@ -584,7 +698,9 @@
     static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
             throws XmlPullParserException, IOException {
         final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
-        final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
+        if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
+            XmlUtils.readIntAttribute(in, ATTR_USER_ID);
+        }
 
         BlobHandle blobHandle = null;
         final ArraySet<Committer> committers = new ArraySet<>();
@@ -608,7 +724,7 @@
             return null;
         }
 
-        final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
+        final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle);
         blobMetadata.setCommitters(committers);
         blobMetadata.setLeasees(leasees);
         return blobMetadata;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 5cebf8d..502b29eb 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -27,12 +27,11 @@
 import android.provider.DeviceConfig.Properties;
 import android.text.TextUtils;
 import android.util.DataUnit;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import com.android.internal.util.IndentingPrintWriter;
-
 import java.io.File;
 import java.util.concurrent.TimeUnit;
 
@@ -47,8 +46,9 @@
     public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
     public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
     public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
+    public static final int XML_VERSION_ALLOW_ACCESS_ACROSS_USERS = 6;
 
-    public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
+    public static final int XML_VERSION_CURRENT = XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
 
     public static final long INVALID_BLOB_ID = 0;
     public static final long INVALID_BLOB_SIZE = 0;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 0e73547..cc5e31a 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -32,6 +32,7 @@
 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
 import static com.android.server.blob.BlobStoreConfig.LOGV;
 import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
@@ -83,6 +84,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -96,7 +98,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -129,6 +130,7 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -146,9 +148,9 @@
     @GuardedBy("mBlobsLock")
     private long mCurrentMaxSessionId;
 
-    // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
+    // Contains data of BlobHandle -> BlobMetadata.
     @GuardedBy("mBlobsLock")
-    private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
+    private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>();
 
     // Contains all ids that are currently in use.
     @GuardedBy("mBlobsLock")
@@ -265,16 +267,6 @@
         return userSessions;
     }
 
-    @GuardedBy("mBlobsLock")
-    private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
-        ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
-        if (userBlobs == null) {
-            userBlobs = new ArrayMap<>();
-            mBlobsMap.put(userId, userBlobs);
-        }
-        return userBlobs;
-    }
-
     @VisibleForTesting
     void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
         synchronized (mBlobsLock) {
@@ -283,9 +275,16 @@
     }
 
     @VisibleForTesting
-    void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
+    BlobMetadata getBlobForTest(BlobHandle blobHandle) {
         synchronized (mBlobsLock) {
-            mBlobsMap.put(userId, userBlobs);
+            return mBlobsMap.get(blobHandle);
+        }
+    }
+
+    @VisibleForTesting
+    int getBlobsCountForTest() {
+        synchronized (mBlobsLock) {
+            return mBlobsMap.size();
         }
     }
 
@@ -319,14 +318,9 @@
     }
 
     @GuardedBy("mBlobsLock")
-    private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
-        addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
-    }
-
-    @GuardedBy("mBlobsLock")
-    private void addBlobForUserLocked(BlobMetadata blobMetadata,
-            ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
-        userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
+    @VisibleForTesting
+    void addBlobLocked(BlobMetadata blobMetadata) {
+        mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata);
         addActiveBlobIdLocked(blobMetadata.getBlobId());
     }
 
@@ -404,8 +398,7 @@
     private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
             String callingPackage, String attributionTag) throws IOException {
         synchronized (mBlobsLock) {
-            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
-                    .get(blobHandle);
+            final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid, attributionTag)) {
                 if (blobMetadata == null) {
@@ -415,7 +408,7 @@
                 } else {
                     FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
                             blobMetadata.getBlobId(), blobMetadata.getSize(),
-                            FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
+                            FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED);
                 }
                 throw new SecurityException("Caller not allowed to access " + blobHandle
                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
@@ -425,7 +418,7 @@
                     blobMetadata.getBlobId(), blobMetadata.getSize(),
                     FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
 
-            return blobMetadata.openForRead(callingPackage);
+            return blobMetadata.openForRead(callingPackage, callingUid);
         }
     }
 
@@ -433,11 +426,11 @@
     private int getCommittedBlobsCountLocked(int uid, String packageName) {
         // TODO: Maintain a counter instead of traversing all the blobs
         final AtomicInteger blobsCount = new AtomicInteger(0);
-        forEachBlobInUser((blobMetadata) -> {
+        forEachBlobLocked(blobMetadata -> {
             if (blobMetadata.isACommitter(packageName, uid)) {
                 blobsCount.getAndIncrement();
             }
-        }, UserHandle.getUserId(uid));
+        });
         return blobsCount.get();
     }
 
@@ -445,11 +438,11 @@
     private int getLeasedBlobsCountLocked(int uid, String packageName) {
         // TODO: Maintain a counter instead of traversing all the blobs
         final AtomicInteger blobsCount = new AtomicInteger(0);
-        forEachBlobInUser((blobMetadata) -> {
+        forEachBlobLocked(blobMetadata -> {
             if (blobMetadata.isALeasee(packageName, uid)) {
                 blobsCount.getAndIncrement();
             }
-        }, UserHandle.getUserId(uid));
+        });
         return blobsCount.get();
     }
 
@@ -465,8 +458,16 @@
                 throw new LimitExceededException("Too many leased blobs for the caller: "
                         + leasesCount);
             }
-            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
-                    .get(blobHandle);
+            if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
+                    && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
+                FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                        INVALID_BLOB_ID, INVALID_BLOB_SIZE,
+                        FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
+                throw new IllegalArgumentException(
+                        "Lease expiry cannot be later than blobs expiry time");
+            }
+
+            final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid, attributionTag)) {
                 if (blobMetadata == null) {
@@ -481,15 +482,7 @@
                 throw new SecurityException("Caller not allowed to access " + blobHandle
                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
             }
-            if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
-                    && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
 
-                FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
-                        blobMetadata.getBlobId(), blobMetadata.getSize(),
-                        FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
-                throw new IllegalArgumentException(
-                        "Lease expiry cannot be later than blobs expiry time");
-            }
             if (blobMetadata.getSize()
                     > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
 
@@ -518,20 +511,18 @@
     @GuardedBy("mBlobsLock")
     long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
         final AtomicLong totalBytes = new AtomicLong(0);
-        forEachBlobInUser((blobMetadata) -> {
+        forEachBlobLocked((blobMetadata) -> {
             if (blobMetadata.isALeasee(callingPackage, callingUid)) {
                 totalBytes.getAndAdd(blobMetadata.getSize());
             }
-        }, UserHandle.getUserId(callingUid));
+        });
         return totalBytes.get();
     }
 
     private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
             String callingPackage, String attributionTag) {
         synchronized (mBlobsLock) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
-                    getUserBlobsLocked(UserHandle.getUserId(callingUid));
-            final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
+            final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid, attributionTag)) {
                 throw new SecurityException("Caller not allowed to access " + blobHandle
@@ -547,12 +538,12 @@
                     synchronized (mBlobsLock) {
                         // Check if blobMetadata object is still valid. If it is not, then
                         // it means that it was already deleted and nothing else to do here.
-                        if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) {
+                        if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) {
                             return;
                         }
                         if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
                             deleteBlobLocked(blobMetadata);
-                            userBlobs.remove(blobHandle);
+                            mBlobsMap.remove(blobHandle);
                         }
                         writeBlobsInfoAsync();
                     }
@@ -583,12 +574,18 @@
                 }
                 return packageResources;
             };
-            getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
+            forEachBlobLocked((blobHandle, blobMetadata) -> {
+                if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) {
+                    return;
+                }
                 final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
                 blobMetadata.forEachLeasee(leasee -> {
                     if (!leasee.isStillValid()) {
                         return;
                     }
+                    if (userId != UserHandle.getUserId(leasee.uid)) {
+                        return;
+                    }
                     final int descriptionResId = leasee.descriptionResEntryName == null
                             ? Resources.ID_NULL
                             : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
@@ -608,9 +605,7 @@
 
     private void deleteBlobInternal(long blobId, int callingUid) {
         synchronized (mBlobsLock) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
-                    UserHandle.getUserId(callingUid));
-            userBlobs.entrySet().removeIf(entry -> {
+            mBlobsMap.entrySet().removeIf(entry -> {
                 final BlobMetadata blobMetadata = entry.getValue();
                 if (blobMetadata.getBlobId() == blobId) {
                     deleteBlobLocked(blobMetadata);
@@ -625,19 +620,20 @@
     private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
             @NonNull String callingPackage) {
         final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
-        forEachBlobInUser(blobMetadata -> {
-            if (blobMetadata.isALeasee(callingPackage, callingUid)) {
-                leasedBlobs.add(blobMetadata.getBlobHandle());
-            }
-        }, UserHandle.getUserId(callingUid));
+        synchronized (mBlobsLock) {
+            forEachBlobLocked(blobMetadata -> {
+                if (blobMetadata.isALeasee(callingPackage, callingUid)) {
+                    leasedBlobs.add(blobMetadata.getBlobHandle());
+                }
+            });
+        }
         return leasedBlobs;
     }
 
     private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
             int callingUid, @NonNull String callingPackage, String attributionTag) {
         synchronized (mBlobsLock) {
-            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
-                    .get(blobHandle);
+            final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid, attributionTag)) {
                 throw new SecurityException("Caller not allowed to access " + blobHandle
@@ -699,14 +695,14 @@
                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED);
                         break;
                     }
-                    final int userId = UserHandle.getUserId(session.getOwnerUid());
-                    final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
-                            userId);
-                    BlobMetadata blob = userBlobs.get(session.getBlobHandle());
-                    if (blob == null) {
+                    final BlobMetadata blob;
+                    final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle());
+                    if (blobIndex >= 0) {
+                        blob = mBlobsMap.valueAt(blobIndex);
+                    } else {
                         blob = new BlobMetadata(mContext, session.getSessionId(),
-                                session.getBlobHandle(), userId);
-                        addBlobForUserLocked(blob, userBlobs);
+                                session.getBlobHandle());
+                        addBlobLocked(blob);
                     }
                     final Committer existingCommitter = blob.getExistingCommitter(
                             session.getOwnerPackageName(), session.getOwnerUid());
@@ -738,7 +734,7 @@
                         // But if it is a recommit, just leave it as is.
                         if (session.getSessionId() == blob.getBlobId()) {
                             deleteBlobLocked(blob);
-                            userBlobs.remove(blob.getBlobHandle());
+                            mBlobsMap.remove(blob.getBlobHandle());
                         }
                     }
                     // Delete redundant data from recommits.
@@ -874,13 +870,10 @@
             out.startTag(null, TAG_BLOBS);
             XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
 
-            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
-                    out.startTag(null, TAG_BLOB);
-                    userBlobs.valueAt(j).writeToXml(out);
-                    out.endTag(null, TAG_BLOB);
-                }
+            for (int i = 0, count = mBlobsMap.size(); i < count; ++i) {
+                out.startTag(null, TAG_BLOB);
+                mBlobsMap.valueAt(i).writeToXml(out);
+                out.endTag(null, TAG_BLOB);
             }
 
             out.endTag(null, TAG_BLOBS);
@@ -925,16 +918,21 @@
                 if (TAG_BLOB.equals(in.getName())) {
                     final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
                             in, version, mContext);
-                    final SparseArray<String> userPackages = allPackages.get(
-                            blobMetadata.getUserId());
-                    if (userPackages == null) {
-                        blobMetadata.getBlobFile().delete();
-                    } else {
-                        addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
-                        blobMetadata.removeCommittersFromUnknownPkgs(userPackages);
-                        blobMetadata.removeLeaseesFromUnknownPkgs(userPackages);
-                    }
+                    blobMetadata.removeCommittersFromUnknownPkgs(allPackages);
+                    blobMetadata.removeLeaseesFromUnknownPkgs(allPackages);
                     mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
+                    if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
+                        addBlobLocked(blobMetadata);
+                    } else {
+                        final BlobMetadata existingBlobMetadata = mBlobsMap.get(
+                                blobMetadata.getBlobHandle());
+                        if (existingBlobMetadata == null) {
+                            addBlobLocked(blobMetadata);
+                        } else {
+                            existingBlobMetadata.addCommittersAndLeasees(blobMetadata);
+                            blobMetadata.getBlobFile().delete();
+                        }
+                    }
                 }
             }
             if (LOGV) {
@@ -977,14 +975,6 @@
         }
     }
 
-    private int getPackageUid(String packageName, int userId) {
-        final int uid = mPackageManagerInternal.getPackageUid(
-                packageName,
-                MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
-                userId);
-        return uid;
-    }
-
     private SparseArray<SparseArray<String>> getAllPackages() {
         final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
         final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
@@ -1004,7 +994,7 @@
         return allPackages;
     }
 
-    AtomicFile prepareSessionsIndexFile() {
+    private AtomicFile prepareSessionsIndexFile() {
         final File file = BlobStoreConfig.prepareSessionIndexFile();
         if (file == null) {
             return null;
@@ -1012,7 +1002,7 @@
         return new AtomicFile(file, "session_index" /* commitLogTag */);
     }
 
-    AtomicFile prepareBlobsIndexFile() {
+    private AtomicFile prepareBlobsIndexFile() {
         final File file = BlobStoreConfig.prepareBlobsIndexFile();
         if (file == null) {
             return null;
@@ -1037,9 +1027,7 @@
             writeBlobSessionsAsync();
 
             // Remove the package from the committer and leasee list
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
-                    getUserBlobsLocked(UserHandle.getUserId(uid));
-            userBlobs.entrySet().removeIf(entry -> {
+            mBlobsMap.entrySet().removeIf(entry -> {
                 final BlobMetadata blobMetadata = entry.getValue();
                 final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
                 if (isACommitter) {
@@ -1074,14 +1062,15 @@
                 }
             }
 
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
-                    mBlobsMap.removeReturnOld(userId);
-            if (userBlobs != null) {
-                for (int i = 0, count = userBlobs.size(); i < count; ++i) {
-                    final BlobMetadata blobMetadata = userBlobs.valueAt(i);
+            mBlobsMap.entrySet().removeIf(entry -> {
+                final BlobMetadata blobMetadata = entry.getValue();
+                blobMetadata.removeDataForUser(userId);
+                if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
                     deleteBlobLocked(blobMetadata);
+                    return true;
                 }
-            }
+                return false;
+            });
             if (LOGV) {
                 Slog.v(TAG, "Removed blobs data in user " + userId);
             }
@@ -1114,22 +1103,19 @@
         }
 
         // Cleanup any stale blobs.
-        for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-            userBlobs.entrySet().removeIf(entry -> {
-                final BlobMetadata blobMetadata = entry.getValue();
+        mBlobsMap.entrySet().removeIf(entry -> {
+            final BlobMetadata blobMetadata = entry.getValue();
 
-                // Remove expired leases
-                blobMetadata.removeExpiredLeases();
+            // Remove expired leases
+            blobMetadata.removeExpiredLeases();
 
-                if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
-                    deleteBlobLocked(blobMetadata);
-                    deletedBlobIds.add(blobMetadata.getBlobId());
-                    return true;
-                }
-                return false;
-            });
-        }
+            if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
+                deleteBlobLocked(blobMetadata);
+                deletedBlobIds.add(blobMetadata.getBlobId());
+                return true;
+            }
+            return false;
+        });
         writeBlobsInfoAsync();
 
         // Cleanup any stale sessions.
@@ -1195,34 +1181,34 @@
 
     void runClearAllBlobs(@UserIdInt int userId) {
         synchronized (mBlobsLock) {
-            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-                final int blobUserId = mBlobsMap.keyAt(i);
-                if (userId != UserHandle.USER_ALL && userId != blobUserId) {
-                    continue;
+            mBlobsMap.entrySet().removeIf(entry -> {
+                final BlobMetadata blobMetadata = entry.getValue();
+                if (userId == UserHandle.USER_ALL) {
+                    mActiveBlobIds.remove(blobMetadata.getBlobId());
+                    return true;
                 }
-                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
-                    mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
+                blobMetadata.removeDataForUser(userId);
+                if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
+                    mActiveBlobIds.remove(blobMetadata.getBlobId());
+                    return true;
                 }
-            }
-            if (userId == UserHandle.USER_ALL) {
-                mBlobsMap.clear();
-            } else {
-                mBlobsMap.remove(userId);
-            }
+                return false;
+            });
             writeBlobsInfoAsync();
         }
     }
 
     void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
         synchronized (mBlobsLock) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
-            final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
+            final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
             if (blobMetadata == null) {
                 return;
             }
-            deleteBlobLocked(blobMetadata);
-            userBlobs.remove(blobHandle);
+            blobMetadata.removeDataForUser(userId);
+            if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
+                deleteBlobLocked(blobMetadata);
+                mBlobsMap.remove(blobHandle);
+            }
             writeBlobsInfoAsync();
         }
     }
@@ -1235,11 +1221,12 @@
 
     boolean isBlobAvailable(long blobId, int userId) {
         synchronized (mBlobsLock) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
-            for (BlobMetadata blobMetadata : userBlobs.values()) {
-                if (blobMetadata.getBlobId() == blobId) {
-                    return true;
+            for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
+                final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
+                if (blobMetadata.getBlobId() != blobId) {
+                    continue;
                 }
+                return blobMetadata.hasACommitterInUser(userId);
             }
             return false;
         }
@@ -1274,27 +1261,22 @@
 
     @GuardedBy("mBlobsLock")
     private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
-        for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-            final int userId = mBlobsMap.keyAt(i);
-            if (!dumpArgs.shouldDumpUser(userId)) {
+        fout.println("List of blobs (" + mBlobsMap.size() + "):");
+        fout.increaseIndent();
+        for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
+            final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
+            if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
                 continue;
             }
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-            fout.println("List of blobs in user #"
-                    + userId + " (" + userBlobs.size() + "):");
+            fout.println("Blob #" + blobMetadata.getBlobId());
             fout.increaseIndent();
-            for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
-                final BlobMetadata blobMetadata = userBlobs.valueAt(j);
-                if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
-                    continue;
-                }
-                fout.println("Blob #" + blobMetadata.getBlobId());
-                fout.increaseIndent();
-                blobMetadata.dump(fout, dumpArgs);
-                fout.decreaseIndent();
-            }
+            blobMetadata.dump(fout, dumpArgs);
             fout.decreaseIndent();
         }
+        if (mBlobsMap.isEmpty()) {
+            fout.println("<empty>");
+        }
+        fout.decreaseIndent();
     }
 
     private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
@@ -1308,13 +1290,12 @@
                 }
             }, userId);
 
-            forEachBlobInUser(blobMetadata -> {
-                if (blobMetadata.isALeasee(packageName)) {
-                    if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
-                        blobsDataSize.getAndAdd(blobMetadata.getSize());
-                    }
+            forEachBlob(blobMetadata -> {
+                if (blobMetadata.shouldAttributeToLeasee(packageName, userId,
+                        callerHasStatsPermission)) {
+                    blobsDataSize.getAndAdd(blobMetadata.getSize());
                 }
-            }, userId);
+            });
 
             stats.dataSize += blobsDataSize.get();
         }
@@ -1330,13 +1311,12 @@
                 }
             }, userId);
 
-            forEachBlobInUser(blobMetadata -> {
-                if (blobMetadata.isALeasee(uid)) {
-                    if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
-                        blobsDataSize.getAndAdd(blobMetadata.getSize());
-                    }
+            forEachBlob(blobMetadata -> {
+                if (blobMetadata.shouldAttributeToLeasee(uid,
+                        callerHasStatsPermission)) {
+                    blobsDataSize.getAndAdd(blobMetadata.getSize());
                 }
-            }, userId);
+            });
 
             stats.dataSize += blobsDataSize.get();
         }
@@ -1352,13 +1332,26 @@
         }
     }
 
-    private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
-        synchronized (mBlobsLock) {
-            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
-            for (int i = 0, count = userBlobs.size(); i < count; ++i) {
-                final BlobMetadata blobMetadata = userBlobs.valueAt(i);
-                consumer.accept(blobMetadata);
-            }
+    private void forEachBlob(Consumer<BlobMetadata> consumer) {
+        synchronized (mBlobsMap) {
+            forEachBlobLocked(consumer);
+        }
+    }
+
+    @GuardedBy("mBlobsMap")
+    private void forEachBlobLocked(Consumer<BlobMetadata> consumer) {
+        for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
+            final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
+            consumer.accept(blobMetadata);
+        }
+    }
+
+    @GuardedBy("mBlobsMap")
+    private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) {
+        for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
+            final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx);
+            final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
+            consumer.accept(blobHandle, blobMetadata);
         }
     }
 
@@ -1886,15 +1879,7 @@
     }
 
     private int pullBlobData(int atomTag, List<StatsEvent> data) {
-        synchronized (mBlobsLock) {
-            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
-                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
-                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
-                    final BlobMetadata blob = userBlobs.valueAt(j);
-                    data.add(blob.dumpAsStatsEvent(atomTag));
-                }
-            }
-        }
+        forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag)));
         return StatsManager.PULL_SUCCESS;
     }
 
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 2c3f682..3f0032f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -56,12 +56,12 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.blob.BlobStoreManagerService.DumpArgs;
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 88f3df8..9ea6f79 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -208,10 +208,10 @@
     public static final int FLAG_PRIORITIZE = 1 << 6;
 
     /**
-     * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
-     * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
-     * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
-     * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+     * For apps targeting {@link Build.VERSION_CODES#S} or above, any APIs setting exact alarms,
+     * e.g. {@link #setExact(int, long, PendingIntent)},
+     * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} and others will require holding a new
+     * permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM}
      *
      * @hide
      */
@@ -219,6 +219,21 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
     public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
 
+    /**
+     * For apps targeting {@link Build.VERSION_CODES#S} or above, all inexact alarms will require
+     * to have a minimum window size, expected to be on the order of a few minutes.
+     *
+     * Practically, any alarms requiring smaller windows are the same as exact alarms and should use
+     * the corresponding APIs provided, like {@link #setExact(int, long, PendingIntent)}, et al.
+     *
+     * Inexact alarm with shorter windows specified will have their windows elongated by the system.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+    public static final long ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS = 185199076L;
+
     @UnsupportedAppUsage
     private final IAlarmManager mService;
     private final Context mContext;
@@ -483,6 +498,11 @@
      * modest timeliness requirements for its alarms.
      *
      * <p>
+     * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window
+     * specified is at least a few minutes, as smaller windows are considered practically exact
+     * and should use the other APIs provided for exact alarms.
+     *
+     * <p>
      * This method can also be used to achieve strict ordering guarantees among
      * multiple alarms by ensuring that the windows requested for each alarm do
      * not intersect.
@@ -532,6 +552,13 @@
      * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
      * invoked via the specified target Handler, or on the application's main looper
      * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * <p>
+     * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window
+     * specified is at least a few minutes, as smaller windows are considered practically exact
+     * and should use the other APIs provided for exact alarms.
+     *
+     * @see #setWindow(int, long, long, PendingIntent)
      */
     public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
             String tag, OnAlarmListener listener, Handler targetHandler) {
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 7a36141..03d9a96 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -452,7 +452,8 @@
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * INTERVAL_DAY;
-        private static final long DEFAULT_MIN_WINDOW = 10_000;
+        // TODO (b/185199076): Tune based on breakage reports.
+        private static final long DEFAULT_MIN_WINDOW = 30 * 60 * 1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
         private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -1683,17 +1684,23 @@
             }
         }
 
-        if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
-            // Do not support windows for idle-until alarms.
-            windowLength = AlarmManager.WINDOW_EXACT;
-        }
-
-        // Sanity check the window length.  This will catch people mistakenly
-        // trying to pass an end-of-window timestamp rather than a duration.
-        if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
+        // Snap the window to reasonable limits.
+        if (windowLength > INTERVAL_DAY) {
             Slog.w(TAG, "Window length " + windowLength
-                    + "ms suspiciously long; limiting to 1 hour");
-            windowLength = AlarmManager.INTERVAL_HOUR;
+                    + "ms suspiciously long; limiting to 1 day");
+            windowLength = INTERVAL_DAY;
+        } 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 "
+                        + mConstants.MIN_WINDOW + "ms.");
+                windowLength = mConstants.MIN_WINDOW;
+            } else {
+                // TODO (b/185199076): Remove log once we have some data about what apps will break
+                Slog.wtf(TAG, "Short window " + windowLength + "ms specified by "
+                        + callingPackage);
+            }
         }
 
         // Sanity check the recurrence interval.  This will catch people who supply
@@ -1730,14 +1737,13 @@
         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);
             // Fix this window in place, so that as time approaches we don't collapse it.
             windowLength = maxElapsed - triggerElapsed;
         } else {
-            windowLength = Math.max(windowLength, mConstants.MIN_WINDOW);
             maxElapsed = triggerElapsed + windowLength;
         }
         synchronized (mLock) {
@@ -2135,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;
@@ -2183,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/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 11a8b3b..500735b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -83,6 +83,7 @@
      * instance.
      */
     private static final long MIN_STATS_UPDATE_INTERVAL_MS = 30_000L;
+    private static final long MIN_ADJUST_CALLBACK_INTERVAL_MS = 1_000L;
 
     private static final int UNBYPASSABLE_BG_BLOCKED_REASONS =
             ~ConnectivityManager.BLOCKED_REASON_NONE;
@@ -210,6 +211,7 @@
      * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
      */
     private final List<UidStats> mSortedStats = new ArrayList<>();
+    private long mLastCallbackAdjustmentTimeElapsed;
 
     private static final int MSG_ADJUST_CALLBACKS = 0;
 
@@ -693,7 +695,11 @@
     }
 
     private void postAdjustCallbacks() {
-        mHandler.obtainMessage(MSG_ADJUST_CALLBACKS).sendToTarget();
+        postAdjustCallbacks(0);
+    }
+
+    private void postAdjustCallbacks(long delayMs) {
+        mHandler.sendEmptyMessageDelayed(MSG_ADJUST_CALLBACKS, delayMs);
     }
 
     @GuardedBy("mLock")
@@ -708,6 +714,12 @@
         }
 
         final long nowElapsed = sElapsedRealtimeClock.millis();
+        if (nowElapsed - mLastCallbackAdjustmentTimeElapsed < MIN_ADJUST_CALLBACK_INTERVAL_MS) {
+            postAdjustCallbacks(MIN_ADJUST_CALLBACK_INTERVAL_MS);
+            return;
+        }
+
+        mLastCallbackAdjustmentTimeElapsed = nowElapsed;
         mSortedStats.clear();
 
         for (int u = 0; u < mUidStats.size(); ++u) {
@@ -926,7 +938,10 @@
         UidDefaultNetworkCallback defaultNetworkCallback =
                 mCurrentDefaultNetworkCallbacks.get(jobs.valueAt(0).getSourceUid());
         if (defaultNetworkCallback == null) {
-            maybeRegisterDefaultNetworkCallbackLocked(jobs.valueAt(0));
+            // This method is only called via a network callback object. That means something
+            // changed about a general network characteristic (since we wouldn't be in this
+            // situation if called from a UID_specific callback). The general network callback
+            // will handle adjusting the per-UID callbacks, so nothing left to do here.
             return false;
         }
 
@@ -1106,8 +1121,13 @@
             synchronized (mLock) {
                 if (Objects.equals(mDefaultNetwork, network)) {
                     mDefaultNetwork = null;
+                    updateTrackedJobsLocked(mUid, network);
+                    // Add a delay in case onAvailable()+onBlockedStatusChanged is called for a
+                    // new network. If this onLost was called because the network is completely
+                    // gone, the delay will hel make sure we don't have a short burst of adjusting
+                    // callback calls.
+                    postAdjustCallbacks(1000);
                 }
-                updateTrackedJobsLocked(mUid, network);
             }
         }
 
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/boot/hiddenapi/OWNERS b/boot/hiddenapi/OWNERS
index 5d869fc..74a3dc0 100644
--- a/boot/hiddenapi/OWNERS
+++ b/boot/hiddenapi/OWNERS
@@ -1,7 +1,5 @@
 # compat-team@ for changes to hiddenapi files
-andreionea@google.com
-mathewi@google.com
-satayev@google.com
+file:tools/platform-compat:/OWNERS
 
 # Escalations:
 per-file hiddenapi-* = bdc@google.com, narayan@google.com
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 37bc44f..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
@@ -1948,18 +1948,20 @@
   }
 
   public static interface BluetoothAdapter.OobDataCallback {
+    method public void onError(int);
+    method public void onOobData(int, @Nullable android.bluetooth.OobData);
   }
 
   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);
@@ -2000,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
@@ -2058,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";
   }
 
@@ -2183,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 {
@@ -7942,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 {
@@ -8966,7 +8968,7 @@
 package android.provider {
 
   public class CallLog {
-    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPictureAsUser(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
+    method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
   }
 
   public static class CallLog.CallComposerLoggingException extends java.lang.Throwable {
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index db42803..a24555f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4272,7 +4272,8 @@
         try {
             getService().broadcastIntentWithFeature(
                     null, null, intent, null, null, Activity.RESULT_OK, null, null,
-                    null /*permission*/, appOp, null, false, true, userId);
+                    null /*requiredPermissions*/, null /*excludedPermissions*/, appOp, null, false,
+                    true, userId);
         } catch (RemoteException ex) {
         }
     }
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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f7ea381..9753b67 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1176,8 +1176,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
-                    false, getUserId());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, null, false, false, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1194,7 +1194,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, null, false, false, getUserId());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+                    getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1209,7 +1210,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, null, false, false, getUserId());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+                    getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1224,7 +1226,24 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions,
+            String[] excludedPermissions) {
+        warnIfCallingFromSystemProcess();
+        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+        try {
+            intent.prepareToLeaveProcess(this);
+            ActivityManager.getService().broadcastIntentWithFeature(
+                    mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
+                    null, Activity.RESULT_OK, null, null, receiverPermissions, excludedPermissions,
+                    AppOpsManager.OP_NONE, null, false, false, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1241,7 +1260,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, options, false, false, getUserId());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
+                    getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1257,8 +1277,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false,
-                    false, getUserId());
+                    null, Activity.RESULT_OK, null, null, receiverPermissions,
+                    null /*excludedPermissions=*/, appOp, null, false, false, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1275,7 +1295,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, null, true, false, getUserId());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, false,
+                    getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1337,8 +1358,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    rd, initialCode, initialData, initialExtras, receiverPermissions, appOp,
-                    options, true, false, getUserId());
+                    rd, initialCode, initialData, initialExtras, receiverPermissions,
+                    null /*excludedPermissions=*/, appOp, options, true, false, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1351,8 +1372,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
-                    false, user.getIdentifier());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1375,7 +1396,8 @@
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    AppOpsManager.OP_NONE, options, false, false, user.getIdentifier());
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
+                    user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1391,8 +1413,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false,
-                    false, user.getIdentifier());
+                    null, Activity.RESULT_OK, null, null, receiverPermissions,
+                    null /*excludedPermissions=*/, appOp, null, false, false, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1442,8 +1464,9 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    rd, initialCode, initialData, initialExtras, receiverPermissions, appOp,
-                    options, true, false, user.getIdentifier());
+                    rd, initialCode, initialData, initialExtras, receiverPermissions,
+                    null /*excludedPermissions=*/, appOp, options, true, false,
+                    user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1483,8 +1506,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
-                    true, getUserId());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, null, false, true, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1522,8 +1545,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options,
-                    false, true, getUserId());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, options, false, true, getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1558,8 +1581,9 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null,
-                    true, true, getUserId());
+                    rd, initialCode, initialData, initialExtras, null,
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+                    getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1590,8 +1614,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
-                    true, user.getIdentifier());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, null, false, true, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1605,8 +1629,8 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options,
-                    false, true, user.getIdentifier());
+                    null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+                    AppOpsManager.OP_NONE, options, false, true, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1640,8 +1664,9 @@
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
-                    rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null,
-                    true, true, user.getIdentifier());
+                    rd, initialCode, initialData, initialExtras, null,
+                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+                    user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f9279da..89d90a3 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -137,7 +137,7 @@
             int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
     int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId,
             in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode,
-            in String resultData, in Bundle map, in String[] requiredPermissions,
+            in String resultData, in Bundle map, in String[] requiredPermissions, in String[] excludePermissions,
             int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
     void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId);
     @UnsupportedAppUsage
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d15d1b7..7ce0c70 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6315,7 +6315,7 @@
          * Gets the theme's background color
          */
         private @ColorInt int getDefaultBackgroundColor() {
-            return obtainThemeColor(R.attr.colorBackground,
+            return obtainThemeColor(R.attr.colorSurface,
                     mInNightMode ? Color.BLACK : Color.WHITE);
         }
 
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 79fd807..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) {
@@ -3095,8 +3209,6 @@
          *
          * @param transport - whether the {@link OobData} is generated for LE or Classic.
          * @param oobData - data generated in the host stack(LE) or controller (Classic)
-         *
-         * @hide
          */
         void onOobData(@Transport int transport, @Nullable OobData oobData);
 
@@ -3104,8 +3216,6 @@
          * Provides feedback when things don't go as expected.
          *
          * @param errorCode - the code descibing the type of error that occurred.
-         *
-         * @hide
          */
         void onError(@OobError int errorCode);
     }
@@ -3188,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
@@ -3232,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;
@@ -3256,6 +3368,7 @@
     /**
      * @hide
      */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub {
         private BluetoothStateChangeCallback mCallback;
 
@@ -3447,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);
     }
@@ -3466,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));
@@ -3563,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()");
@@ -3604,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 =
@@ -3650,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 =
@@ -3695,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()");
@@ -3768,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()");
@@ -3857,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()");
@@ -3899,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/OobData.java b/core/java/android/bluetooth/OobData.java
index 08d694e..d6868e0 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -830,7 +830,7 @@
     @Nullable
     @SystemApi
     public byte[] getLeAppearance() {
-        return mLeTemporaryKey;
+        return mLeAppearance;
     }
 
     /**
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/Context.java b/core/java/android/content/Context.java
index 09ac810..318913f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2208,6 +2208,17 @@
     }
 
     /**
+     * Like {@link #sendBroadcastMultiplePermissions(Intent, String[])}, but also allows
+     * specification of a list of excluded permissions. This allows sending a broadcast to an
+     * app that has the permissions in `receiverPermissions` but not `excludedPermissions`.
+     * @hide
+     */
+    public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+            @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Broadcast the given intent to all interested BroadcastReceivers, allowing
      * an array of required permissions to be enforced.  This call is asynchronous; it returns
      * immediately, and you will continue executing while the receivers are run.  No results are
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 8936d0c..dddcbea 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -493,6 +493,13 @@
 
     /** @hide */
     @Override
+    public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+            @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+        mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions);
+    }
+
+    /** @hide */
+    @Override
     public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
             String[] receiverPermissions) {
         mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
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/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/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/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index adc668f..77bd147 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -32,7 +32,9 @@
 
 import com.android.internal.util.CollectionUtils;
 
+import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -350,6 +352,8 @@
      *
      * The set will be ordered from lowest to highest priority.
      *
+     * @param domain The host to query for. An invalid domain will result in an empty set.
+     *
      * @hide
      */
     @SystemApi
@@ -357,11 +361,11 @@
     @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
     public SortedSet<DomainOwner> getOwnersForDomain(@NonNull String domain) {
         try {
+            Objects.requireNonNull(domain);
             final List<DomainOwner> orderedList = mDomainVerificationManager.getOwnersForDomain(
                     domain, mContext.getUserId());
             SortedSet<DomainOwner> set = new TreeSet<>(
-                    (first, second) -> Integer.compare(orderedList.indexOf(first),
-                            orderedList.indexOf(second)));
+                    Comparator.comparingInt(orderedList::indexOf));
             set.addAll(orderedList);
             return set;
         } catch (RemoteException e) {
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/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
index 62d727c..1268658 100644
--- a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
@@ -16,11 +16,9 @@
 
 package android.hardware.biometrics;
 
-import android.hardware.biometrics.BiometricSourceType;
-
 /**
  * @hide
  */
 oneway interface IBiometricEnabledOnKeyguardCallback {
-    void onChanged(in BiometricSourceType type, boolean enabled, int userId);
+    void onChanged(boolean enabled, int userId);
 }
\ No newline at end of file
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/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index b558edc..ba63ba4 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -36,7 +36,7 @@
      * @hide
      */
     @IntDef(prefix = {"POWER_COMPONENT_"}, value = {
-            POWER_COMPONENT_USAGE,
+            POWER_COMPONENT_SCREEN,
             POWER_COMPONENT_CPU,
             POWER_COMPONENT_BLUETOOTH,
             POWER_COMPONENT_CAMERA,
@@ -49,14 +49,16 @@
             POWER_COMPONENT_GNSS,
             POWER_COMPONENT_WIFI,
             POWER_COMPONENT_WAKELOCK,
-            POWER_COMPONENT_SCREEN,
+            POWER_COMPONENT_MEMORY,
+            POWER_COMPONENT_PHONE,
+            POWER_COMPONENT_IDLE,
             POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface PowerComponent {
     }
 
-    public static final int POWER_COMPONENT_USAGE = 0;
+    public static final int POWER_COMPONENT_SCREEN = 0;
     public static final int POWER_COMPONENT_CPU = 1;
     public static final int POWER_COMPONENT_BLUETOOTH = 2;
     public static final int POWER_COMPONENT_CAMERA = 3;
@@ -69,13 +71,15 @@
     public static final int POWER_COMPONENT_GNSS = 10;
     public static final int POWER_COMPONENT_WIFI = 11;
     public static final int POWER_COMPONENT_WAKELOCK = 12;
-    public static final int POWER_COMPONENT_SCREEN = 13;
+    public static final int POWER_COMPONENT_MEMORY = 13;
+    public static final int POWER_COMPONENT_PHONE = 13;
+    public static final int POWER_COMPONENT_IDLE = 15;
     // Power that is re-attributed to other battery consumers. For example, for System Server
     // this represents the power attributed to apps requesting system services.
     // The value should be negative or zero.
-    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 14;
+    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 16;
 
-    public static final int POWER_COMPONENT_COUNT = 15;
+    public static final int POWER_COMPONENT_COUNT = 17;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -87,7 +91,7 @@
      * @hide
      */
     @IntDef(prefix = {"TIME_COMPONENT_"}, value = {
-            TIME_COMPONENT_USAGE,
+            TIME_COMPONENT_SCREEN,
             TIME_COMPONENT_CPU,
             TIME_COMPONENT_CPU_FOREGROUND,
             TIME_COMPONENT_BLUETOOTH,
@@ -98,13 +102,15 @@
             TIME_COMPONENT_GNSS,
             TIME_COMPONENT_WIFI,
             TIME_COMPONENT_WAKELOCK,
-            TIME_COMPONENT_SCREEN,
+            TIME_COMPONENT_MEMORY,
+            TIME_COMPONENT_PHONE,
+            TIME_COMPONENT_IDLE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface TimeComponent {
     }
 
-    public static final int TIME_COMPONENT_USAGE = 0;
+    public static final int TIME_COMPONENT_SCREEN = 0;
     public static final int TIME_COMPONENT_CPU = 1;
     public static final int TIME_COMPONENT_CPU_FOREGROUND = 2;
     public static final int TIME_COMPONENT_BLUETOOTH = 3;
@@ -117,9 +123,11 @@
     public static final int TIME_COMPONENT_GNSS = 10;
     public static final int TIME_COMPONENT_WIFI = 11;
     public static final int TIME_COMPONENT_WAKELOCK = 12;
-    public static final int TIME_COMPONENT_SCREEN = 13;
+    public static final int TIME_COMPONENT_MEMORY = 13;
+    public static final int TIME_COMPONENT_PHONE = 14;
+    public static final int TIME_COMPONENT_IDLE = 15;
 
-    public static final int TIME_COMPONENT_COUNT = 14;
+    public static final int TIME_COMPONENT_COUNT = 16;
 
     public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
@@ -148,8 +156,7 @@
      */
     public static final int POWER_MODEL_MEASURED_ENERGY = 1;
 
-    private final PowerComponents mPowerComponents;
-    private String[] mCustomPowerComponentNames;
+    protected final PowerComponents mPowerComponents;
 
     protected BatteryConsumer(@NonNull PowerComponents powerComponents) {
         mPowerComponents = powerComponents;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 48f4ca4..8ea59ce 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -24,7 +24,6 @@
 import com.android.internal.os.BatteryStatsHistoryIterator;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -233,8 +232,6 @@
             mHistoryBuffer = null;
             mHistoryTagPool = null;
         }
-        System.out.println("From Parcel = " + Arrays.toString(
-                mCustomPowerComponentNames));
     }
 
     @Override
@@ -293,6 +290,7 @@
      * Builder for BatteryUsageStats.
      */
     public static final class Builder {
+        @NonNull
         private final String[] mCustomPowerComponentNames;
         private final int mCustomTimeComponentCount;
         private final boolean mIncludePowerModels;
@@ -311,11 +309,11 @@
         private Parcel mHistoryBuffer;
         private List<BatteryStats.HistoryTag> mHistoryTagPool;
 
-        public Builder(String[] customPowerComponentNames, int customTimeComponentCount) {
+        public Builder(@NonNull String[] customPowerComponentNames, int customTimeComponentCount) {
             this(customPowerComponentNames, customTimeComponentCount, false);
         }
 
-        public Builder(String[] customPowerComponentNames, int customTimeComponentCount,
+        public Builder(@NonNull String[] customPowerComponentNames, int customTimeComponentCount,
                 boolean includePowerModels) {
             mCustomPowerComponentNames = customPowerComponentNames;
             mCustomTimeComponentCount = customTimeComponentCount;
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 2fea4ac..a0a41f4 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -192,6 +192,19 @@
     }
 
     /**
+     * Returns the largest usage duration among all time components.
+     */
+    public long getMaxComponentUsageDurationMillis() {
+        long max = 0;
+        for (int i = mTimeComponentsMs.length - 1; i >= 0; i--) {
+            if (mTimeComponentsMs[i] > max) {
+                max = mTimeComponentsMs[i];
+            }
+        }
+        return max;
+    }
+
+    /**
      * Builder for PowerComponents.
      */
     static final class Builder {
@@ -312,10 +325,10 @@
         }
 
         public void addPowerAndDuration(Builder other) {
-            for (int i = 0; i < mPowerComponentsMah.length; i++) {
+            for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) {
                 mPowerComponentsMah[i] += other.mPowerComponentsMah[i];
             }
-            for (int i = 0; i < mTimeComponentsMs.length; i++) {
+            for (int i = mTimeComponentsMs.length - 1; i >= 0; i--) {
                 mTimeComponentsMs[i] += other.mTimeComponentsMs[i];
             }
         }
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
index 70b0532..1327978 100644
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ b/core/java/android/os/SystemBatteryConsumer.java
@@ -104,6 +104,13 @@
     }
 
     /**
+     * Returns the amount of time this consumer was operating.
+     */
+    public long getUsageDurationMillis() {
+        return mPowerComponents.getMaxComponentUsageDurationMillis();
+    }
+
+    /**
      * Writes the contents into a Parcel.
      */
     @Override
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/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 9bfd75e..51f19eb 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -22,10 +22,10 @@
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.UserHandleAware;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -129,7 +129,7 @@
         public static final int ERROR_STORAGE_FULL = 2;
 
         /**
-         * Indicates that the {@link InputStream} passed to {@link #storeCallComposerPictureAsUser}
+         * Indicates that the {@link InputStream} passed to {@link #storeCallComposerPicture}
          * was closed.
          *
          * The caller should retry if this error is encountered, and be sure to not close the stream
@@ -195,9 +195,8 @@
      * The caller is responsible for closing the {@link InputStream} after the callback indicating
      * success or failure.
      *
-     * @param context An instance of {@link Context}.
-     * @param user The user for whom the picture is stored. If {@code null}, the picture will be
-     *             stored for all users.
+     * @param context An instance of {@link Context}. The picture will be stored to the user
+     *                corresponding to {@link Context#getUser()}.
      * @param input An input stream from which the picture to store should be read. The input data
      *              must be decodeable as either a JPEG, PNG, or GIF image.
      * @param executor The {@link Executor} on which to perform the file transfer operation and
@@ -207,12 +206,12 @@
      * @hide
      */
     @SystemApi
+    @UserHandleAware
     @RequiresPermission(allOf = {
             Manifest.permission.WRITE_CALL_LOG,
             Manifest.permission.INTERACT_ACROSS_USERS
     })
-    public static void storeCallComposerPictureAsUser(@NonNull Context context,
-            @Nullable UserHandle user,
+    public static void storeCallComposerPicture(@NonNull Context context,
             @NonNull InputStream input,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<Uri, CallComposerLoggingException> callback) {
@@ -246,12 +245,13 @@
             byte[] picData = tmpOut.toByteArray();
 
             UserManager userManager = context.getSystemService(UserManager.class);
+            UserHandle user = context.getUser();
             // Nasty casework for the shadow calllog begins...
             // First see if we're just inserting for one user. If so, insert into the shadow
             // based on whether that user is unlocked.
             UserHandle realUser = UserHandle.CURRENT.equals(user)
                     ? android.os.Process.myUserHandle() : user;
-            if (realUser != null) {
+            if (realUser != UserHandle.ALL) {
                 Uri baseUri = userManager.isUserUnlocked(realUser) ? CALL_COMPOSER_PICTURE_URI
                         : SHADOW_CALL_COMPOSER_PICTURE_URI;
                 Uri pictureInsertionUri = ContentProvider.maybeAddUserId(baseUri,
@@ -625,7 +625,7 @@
             }
 
             /**
-             * @param pictureUri {@link Uri} returned from {@link #storeCallComposerPictureAsUser}.
+             * @param pictureUri {@link Uri} returned from {@link #storeCallComposerPicture}.
              *                   Associates that stored picture with this call in the log.
              */
             public @NonNull AddCallParametersBuilder setPictureUri(@NonNull Uri pictureUri) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9450994..2616a667 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9136,6 +9136,20 @@
                 "biometric_debug_enabled";
 
         /**
+         * Whether or not biometric is allowed on Keyguard.
+         * @hide
+         */
+        @Readable
+        public static final String BIOMETRIC_KEYGUARD_ENABLED = "biometric_keyguard_enabled";
+
+        /**
+         * Whether or not biometric is allowed for apps (through BiometricPrompt).
+         * @hide
+         */
+        @Readable
+        public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+
+        /**
          * Whether the assist gesture should be enabled.
          *
          * @hide
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 e450061..21f75d4 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -104,6 +104,7 @@
     private static native void nativeApplyTransaction(long transactionObj, boolean sync);
     private static native void nativeMergeTransaction(long transactionObj,
             long otherTransactionObj);
+    private static native void nativeClearTransaction(long transactionObj);
     private static native void nativeSetAnimationTransaction(long transactionObj);
     private static native void nativeSetEarlyWakeupStart(long transactionObj);
     private static native void nativeSetEarlyWakeupEnd(long transactionObj);
@@ -2602,6 +2603,19 @@
         }
 
         /**
+         * Clear the transaction object, without applying it.
+         *
+         * @hide
+         */
+        public void clear() {
+            mResizedSurfaces.clear();
+            mReparentedSurfaces.clear();
+            if (mNativeObject != 0) {
+                nativeClearTransaction(mNativeObject);
+            }
+        }
+
+        /**
          * Release the native transaction object, without applying it.
          */
         @Override
@@ -3411,10 +3425,14 @@
         public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
             if (mNativeObject == 0) {
                 dest.writeInt(0);
-            } else {
-                dest.writeInt(1);
+                return;
             }
+
+            dest.writeInt(1);
             nativeWriteTransactionToParcel(mNativeObject, dest);
+            if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+                nativeClearTransaction(mNativeObject);
+            }
         }
 
         private void readFromParcel(Parcel in) {
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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e8a4bb7..76eb882 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3906,7 +3906,10 @@
             mDrawsNeededToReport = 0;
             mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
         } catch (RemoteException e) {
-            // Have fun!
+            Log.e(mTag, "Unable to report draw finished", e);
+            mSurfaceChangedTransaction.apply();
+        } finally {
+            mSurfaceChangedTransaction.clear();
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6edd071..616910a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2230,9 +2230,7 @@
     public void removeImeSurface(IBinder windowToken) {
         synchronized (mH) {
             try {
-                final Completable.Void value = Completable.createVoid();
-                mService.removeImeSurfaceFromWindow(windowToken, ResultCallbacks.of(value));
-                Completable.getResult(value);
+                mService.removeImeSurfaceFromWindowAsync(windowToken);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index f4fdf35..c09e8bd 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -302,6 +302,11 @@
                 }
                 final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
                         translatedResult.valueAt(i);
+                if (DEBUG) {
+                    // TODO(b/182433547): remove before S release
+                    Log.v(TAG, "onVirtualViewTranslationCompleted: receive "
+                            + virtualChildResponse + " for AutofillId " + autofillId);
+                }
                 mActivity.runOnUiThread(() -> {
                     if (view.getViewTranslationCallback() == null) {
                         if (DEBUG) {
@@ -341,8 +346,13 @@
             }
             for (int i = 0; i < resultCount; i++) {
                 final ViewTranslationResponse response = translatedResult.valueAt(i);
+                if (DEBUG) {
+                    // TODO(b/182433547): remove before S release
+                    Log.v(TAG, "onTranslationCompleted: response= " + response);
+                }
                 final AutofillId autofillId = response.getAutofillId();
                 if (autofillId == null) {
+                    Log.w(TAG, "No AutofillId is set in ViewTranslationResponse:" + response);
                     continue;
                 }
                 final View view = mViews.get(autofillId).get();
@@ -394,6 +404,9 @@
         final TranslationRequest request = new TranslationRequest.Builder()
                 .setViewTranslationRequests(requests)
                 .build();
+        if (DEBUG) {
+            Log.d(TAG, "sendTranslationRequest: request= " + request);
+        }
         translator.requestUiTranslate(request, (r) -> r.run(), this::onTranslationCompleted);
     }
 
@@ -508,10 +521,17 @@
     private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
         synchronized (mLock) {
             final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
+            if (views.size() == 0) {
+                Log.w(TAG, "No views can be excuted for runForEachView.");
+            }
             mActivity.runOnUiThread(() -> {
                 final int viewCounts = views.size();
                 for (int i = 0; i < viewCounts; i++) {
                     final View view = views.valueAt(i).get();
+                    if (DEBUG) {
+                        // TODO(b/182433547): remove before S release
+                        Log.d(TAG, "runForEachView: view= " + view);
+                    }
                     if (view == null || view.getViewTranslationCallback() == null) {
                         if (DEBUG) {
                             Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid "
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index cd91d02..95a3dc7 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -104,25 +104,31 @@
 
     /**
      * The velocity threshold before the spring animation is considered settled.
-     * The idea here is that velocity should be less than 1 pixel per frame (~16ms).
+     * The idea here is that velocity should be less than 0.1 pixel per second.
      */
-    private static final double VELOCITY_THRESHOLD = 1.0 / 0.016;
+    private static final double VELOCITY_THRESHOLD = 0.1;
 
     /**
      * The value threshold before the spring animation is considered close enough to
-     * the destination to be settled. This should be around 1 pixel.
+     * the destination to be settled. This should be around 0.01 pixel.
      */
-    private static final double VALUE_THRESHOLD = 1;
+    private static final double VALUE_THRESHOLD = 0.01;
 
     /**
      * The natural frequency of the stretch spring.
      */
-    private static final double NATURAL_FREQUENCY = 17.55;
+    private static final double NATURAL_FREQUENCY = 24.657;
 
     /**
      * The damping ratio of the stretch spring.
      */
-    private static final double DAMPING_RATIO = 0.92;
+    private static final double DAMPING_RATIO = 0.98;
+
+    /**
+     * The variation of the velocity for the stretch effect when it meets the bound.
+     * if value is > 1, it will accentuate the absorption of the movement.
+     */
+    private static final float ON_ABSORB_VELOCITY_ADJUSTMENT = 13f;
 
     /** @hide */
     @IntDef({TYPE_GLOW, TYPE_STRETCH})
@@ -460,7 +466,7 @@
     public void onAbsorb(int velocity) {
         if (mEdgeEffectType == TYPE_STRETCH) {
             mState = STATE_RECEDE;
-            mVelocity = velocity;
+            mVelocity = velocity * ON_ABSORB_VELOCITY_ADJUSTMENT;
             mDistance = 0;
             mStartTime = AnimationUtils.currentAnimationTimeMillis();
         } else {
@@ -782,8 +788,8 @@
      * considered at rest or false if it is still animating.
      */
     private boolean isAtEquilibrium() {
-        double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
-        double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+        double displacement = mDistance * mHeight; // in pixels
+        double velocity = mVelocity;
         return Math.abs(velocity) < VELOCITY_THRESHOLD
                 && Math.abs(displacement) < VALUE_THRESHOLD;
     }
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 296d93c..a479b8a 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.NonNull;
+import android.os.Build;
 import android.text.method.TranslationTransformationMethod;
 import android.util.Log;
 import android.view.View;
@@ -34,7 +35,10 @@
 
     private static final String TAG = "TextViewTranslationCallback";
 
-    private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+    // TODO(b/182433547): remove Build.IS_DEBUGGABLE before ship. Enable the logging in debug build
+    //  to help the debug during the development phase
+    private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)
+            || Build.IS_DEBUGGABLE;
 
     private TranslationTransformationMethod mTranslationTransformation;
 
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/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 2f49582..aa416c5 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -52,8 +52,8 @@
                 measuredEnergyUC, mPowerEstimator, durationMs);
         builder.getOrCreateSystemBatteryConsumerBuilder(
                         SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah, powerModel)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah, powerModel)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN, durationMs);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e83f365..cb1900f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6951,9 +6951,9 @@
     /**
      * Returns the names of custom power components.
      */
-    public @Nullable String[] getCustomPowerComponentNames() {
+    public @NonNull String[] getCustomPowerComponentNames() {
         if (mGlobalMeasuredEnergyStats == null) {
-            return null;
+            return new String[0];
         }
         final String[] names = mGlobalMeasuredEnergyStats.getCustomBucketNames();
         for (int i = 0; i < names.length; i++) {
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index 59cc66d..4ca59be 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -16,22 +16,37 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
 import android.annotation.Nullable;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.Random;
 
 /** Collects statistics about Binder call latency per calling API and method. */
 public class BinderLatencyObserver {
     private static final String TAG = "BinderLatencyObserver";
+    private static final int MAX_ATOM_SIZE_BYTES = 4064;
+    // Be conservative and leave 1K space for the last histogram so we don't go over the size limit.
+    private static final int LAST_HISTOGRAM_BUFFER_SIZE_BYTES = 1000;
+
+    // Latency observer parameters.
     public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+    public static final int STATSD_PUSH_INTERVAL_MINUTES_DEFAULT = 360;
 
     // Histogram buckets parameters.
     public static final int BUCKET_COUNT_DEFAULT = 100;
@@ -50,20 +65,124 @@
     private int mFirstBucketSize = FIRST_BUCKET_SIZE_DEFAULT;
     private float mBucketScaleFactor = BUCKET_SCALE_FACTOR_DEFAULT;
 
+    private int mStatsdPushIntervalMinutes = STATSD_PUSH_INTERVAL_MINUTES_DEFAULT;
+
     private final Random mRandom;
     private BinderLatencyBuckets mLatencyBuckets;
 
+    private final Handler mLatencyObserverHandler;
+
+    private Runnable mLatencyObserverRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // Schedule the next push.
+            noteLatencyDelayed();
+
+            ArrayMap<LatencyDims, int[]> histogramMap;
+            synchronized (mLock) {
+                // Copy the histograms map so we don't use the lock for longer than needed.
+                histogramMap = new ArrayMap<>(mLatencyHistograms);
+                mLatencyHistograms.clear();
+            }
+
+            BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+            ProtoOutputStream proto = new ProtoOutputStream();
+            int histogramsWritten = 0;
+
+            for (LatencyDims dims : histogramMap.keySet()) {
+                // Start a new atom if the next histogram risks going over the atom size limit.
+                if (proto.getRawSize() + LAST_HISTOGRAM_BUFFER_SIZE_BYTES > getMaxAtomSizeBytes()) {
+                    if (histogramsWritten > 0) {
+                        writeAtomToStatsd(proto);
+                    }
+                    proto = new ProtoOutputStream();
+                    histogramsWritten = 0;
+                }
+
+                String transactionName = resolver.getMethodName(
+                        dims.getBinderClass(), dims.getTransactionCode());
+                fillApiStatsProto(proto, dims, transactionName, histogramMap.get(dims));
+                histogramsWritten++;
+            }
+            // Push the final atom.
+            if (histogramsWritten > 0) {
+                writeAtomToStatsd(proto);
+            }
+        }
+    };
+
+    private void fillApiStatsProto(
+            ProtoOutputStream proto, LatencyDims dims, String transactionName, int[] histogram) {
+        // Find the part of the histogram to write.
+        int firstNonEmptyBucket = 0;
+        for (int i = 0; i < mBucketCount; i++) {
+            if (histogram[i] != 0) {
+                firstNonEmptyBucket = i;
+                break;
+            }
+        }
+        int lastNonEmptyBucket = mBucketCount - 1;
+        for (int i = mBucketCount - 1; i >= 0; i--) {
+            if (histogram[i] != 0) {
+                lastNonEmptyBucket = i;
+                break;
+            }
+        }
+
+        // Start a new ApiStats proto.
+        long apiStatsToken = proto.start(RepeatedApiStats.API_STATS);
+
+        // Write the dims.
+        long dimsToken = proto.start(ApiStats.DIMS);
+        proto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        proto.write(Dims.SERVICE_CLASS_NAME, dims.getBinderClass().getName());
+        proto.write(Dims.SERVICE_METHOD_NAME, transactionName);
+        proto.end(dimsToken);
+
+        // Write the histogram.
+        proto.write(ApiStats.FIRST_BUCKET_INDEX, firstNonEmptyBucket);
+        for (int i = firstNonEmptyBucket; i <= lastNonEmptyBucket; i++) {
+            proto.write(ApiStats.BUCKETS, histogram[i]);
+        }
+
+        proto.end(apiStatsToken);
+    }
+
+    protected int getMaxAtomSizeBytes() {
+        return MAX_ATOM_SIZE_BYTES;
+    }
+
+    protected void writeAtomToStatsd(ProtoOutputStream atom) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.BINDER_LATENCY_REPORTED,
+                atom.getBytes(),
+                mPeriodicSamplingInterval,
+                1);
+    }
+
+    private void noteLatencyDelayed() {
+        mLatencyObserverHandler.removeCallbacks(mLatencyObserverRunnable);
+        mLatencyObserverHandler.postDelayed(mLatencyObserverRunnable,
+                mStatsdPushIntervalMinutes * 60 * 1000);
+    }
+
     /** Injector for {@link BinderLatencyObserver}. */
     public static class Injector {
         public Random getRandomGenerator() {
             return new Random();
         }
+
+        public Handler getHandler() {
+            return new Handler(Looper.getMainLooper());
+        }
     }
 
     public BinderLatencyObserver(Injector injector) {
         mRandom = injector.getRandomGenerator();
+        mLatencyObserverHandler = injector.getHandler();
         mLatencyBuckets = new BinderLatencyBuckets(
             mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+        noteLatencyDelayed();
     }
 
     /** Should be called when a Binder call completes, will store latency data. */
@@ -73,7 +192,8 @@
         }
 
         LatencyDims dims = new LatencyDims(s.binderClass, s.transactionCode);
-        long callDuration = getElapsedRealtimeMicro() - s.timeStarted;
+        long elapsedTimeMicro = getElapsedRealtimeMicro();
+        long callDuration = elapsedTimeMicro - s.timeStarted;
 
         // Find the bucket this sample should go to.
         int bucketIdx = mLatencyBuckets.sampleToBucket(
@@ -117,6 +237,22 @@
         }
     }
 
+    /** Updates the statsd push interval. */
+    public void setPushInterval(int pushIntervalMinutes) {
+        if (pushIntervalMinutes <= 0) {
+            Slog.w(TAG, "Ignored invalid push interval (value must be positive): "
+                    + pushIntervalMinutes);
+            return;
+        }
+
+        synchronized (mLock) {
+            if (pushIntervalMinutes != mStatsdPushIntervalMinutes) {
+                mStatsdPushIntervalMinutes = pushIntervalMinutes;
+                reset();
+            }
+        }
+    }
+
     /** Updates the histogram buckets parameters. */
     public void setHistogramBucketsParams(
             int bucketCount, int firstBucketSize, float bucketScaleFactor) {
@@ -138,6 +274,7 @@
         synchronized (mLock) {
             mLatencyHistograms.clear();
         }
+        noteLatencyDelayed();
     }
 
     /** Container for binder latency information. */
@@ -187,4 +324,9 @@
     public ArrayMap<LatencyDims, int[]> getLatencyHistograms() {
         return mLatencyHistograms;
     }
+
+    @VisibleForTesting
+    public Runnable getStatsdPushRunnable() {
+        return mLatencyObserverRunnable;
+    }
 }
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 4a4991b..5cb54bd 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -54,8 +54,8 @@
                 BatteryStats.STATS_SINCE_CHARGED);
         if (mPowerMah != 0) {
             builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_IDLE)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, mPowerMah)
-                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, mDurationMs);
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE, mPowerMah)
+                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_IDLE, mDurationMs);
         }
     }
 
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 21dcce9..9ec40c6 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -32,8 +32,8 @@
         final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_MEMORY)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MEMORY, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah);
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 362ca07..6f279d99 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -45,8 +45,8 @@
         final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
         if (phoneOnPower != 0) {
             builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_PHONE)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, phoneOnPower)
-                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, phoneOnTimeMs);
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
+                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_PHONE, phoneOnTimeMs);
         }
     }
 
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 0267def..0743c89 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -96,9 +96,9 @@
         }
 
         builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN,
                         totalPowerAndDuration.durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
                         Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
                 .setPowerConsumedByApps(totalAppPower);
     }
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/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 50bbfc5..fd13c26b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -89,8 +89,7 @@
     /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
     oneway void removeImeSurface(in IVoidResultCallback resultCallback);
     /** Remove the IME surface. Requires passing the currently focused window. */
-    oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
-            in IVoidResultCallback resultCallback);
+    oneway void removeImeSurfaceFromWindowAsync(in IBinder windowToken);
     oneway void startProtoDump(in byte[] protoDump, int source, String where,
             in IVoidResultCallback resultCallback);
     oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
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/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 7a8ead6..de3b6a4 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -165,15 +165,23 @@
 
     private void updateColors() {
         if (shouldShowNumber() && !mDisallowColor) {
-            mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+            if (mHighlightPillColor != 0) {
+                mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+            }
             mPillView.setBackgroundTintMode(PorterDuff.Mode.SRC_IN);
             mIconView.setColorFilter(mHighlightTextColor, PorterDuff.Mode.SRC_IN);
-            mNumberView.setTextColor(mHighlightTextColor);
+            if (mHighlightTextColor != 0) {
+                mNumberView.setTextColor(mHighlightTextColor);
+            }
         } else {
-            mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+            if (mDefaultPillColor != 0) {
+                mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+            }
             mPillView.setBackgroundTintMode(PorterDuff.Mode.SRC_IN);
             mIconView.setColorFilter(mDefaultTextColor, PorterDuff.Mode.SRC_IN);
-            mNumberView.setTextColor(mDefaultTextColor);
+            if (mDefaultTextColor != 0) {
+                mNumberView.setTextColor(mDefaultTextColor);
+            }
         }
     }
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b342755..ffba628 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1562,6 +1562,13 @@
             reinterpret_cast<SurfaceComposerClient::Transaction *>(nativeObject);
     if (self != nullptr) {
         self->writeToParcel(parcel);
+    }
+}
+
+static void nativeClearTransaction(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    SurfaceComposerClient::Transaction* const self =
+            reinterpret_cast<SurfaceComposerClient::Transaction*>(nativeObject);
+    if (self != nullptr) {
         self->clear();
     }
 }
@@ -1880,6 +1887,8 @@
             (void*)nativeReadTransactionFromParcel },
     {"nativeWriteTransactionToParcel", "(JLandroid/os/Parcel;)V",
             (void*)nativeWriteTransactionToParcel },
+    {"nativeClearTransaction", "(J)V",
+            (void*)nativeClearTransaction },
     {"nativeMirrorSurface", "(J)J",
             (void*)nativeMirrorSurface },
     {"nativeSetGlobalShadowSettings", "([F[FFFF)V",
diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto
new file mode 100644
index 0000000..e32c3e3
--- /dev/null
+++ b/core/proto/android/internal/binder_latency.proto
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.internal.os;
+
+option java_outer_classname = "BinderLatencyProto";
+
+/**
+ * RepeatedApiStats proto from atoms.proto, duplicated here so that it's
+ * accessible in the build.
+ * Must be kept in sync with the version in atoms.proto.
+ */
+
+message RepeatedApiStats {
+  repeated ApiStats api_stats = 1;
+}
+
+message Dims {
+  enum ProcessSource {
+    UNKNOWN_PROCESS_SOURCE = 0;
+    SYSTEM_SERVER = 1;
+    TELEPHONY = 2;
+  }
+
+  enum ServiceClassName {
+    UNKNOWN_CLASS = 0;
+  }
+  enum ServiceMethodName {
+    UNKNOWN_METHOD = 0;
+  }
+
+  // Required.
+  optional ProcessSource process_source = 1;
+
+  // The class name of the API making the call to Binder. Enum value
+  // is preferred as uses much less data to store.
+  // This field does not contain PII.
+  oneof service_class {
+    ServiceClassName service_class_name_as_enum = 2;
+    string service_class_name = 3;
+  }
+
+  // Method name of the API call. It can also be a transaction code if we
+  // cannot resolve it to a name. See Binder#getTransactionName. Enum value
+  // is preferred as uses much less data to store.
+  // This field does not contain PII.
+  oneof service_method {
+    ServiceMethodName service_method_name_as_enum = 4;
+    string service_method_name = 5;
+  }
+}
+
+message ApiStats {
+  // required.
+  optional Dims dims = 1;
+
+  // Indicates the first bucket that had any data. Allows omitting any empty
+  // buckets at the start of the bucket list and thus save on data size.
+  optional int32 first_bucket_index = 2;
+  // Stores the count of samples for each bucket. The number of buckets and
+  // their sizes are controlled server side with a flag.
+  repeated int32 buckets = 3;
+}
\ 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/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index b969fa4..e752431 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -22,7 +22,6 @@
     android:layout_gravity="top|end"
     android:contentDescription="@string/expand_button_content_description_collapsed"
     android:padding="16dp"
-    android:visibility="gone"
     >
 
     <LinearLayout
@@ -40,6 +39,7 @@
             android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
             android:gravity="center_vertical"
             android:paddingStart="8dp"
+            android:visibility="gone"
             />
 
         <ImageView
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
index e01d803..389637eb 100644
--- a/core/res/res/layout/notification_template_conversation_header.xml
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
-    android:paddingTop="16dp"
+    android:paddingTop="20dp"
     >
 
     <TextView
@@ -42,8 +42,6 @@
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
-        android:layout_gravity="center"
-        android:paddingTop="1sp"
         android:singleLine="true"
         android:visibility="gone"
         />
@@ -54,10 +52,8 @@
         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-        android:paddingTop="1sp"
         android:singleLine="true"
         android:visibility="gone"
         />
@@ -70,8 +66,6 @@
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
-        android:layout_gravity="center"
-        android:paddingTop="1sp"
         android:singleLine="true"
         android:visibility="gone"
         />
@@ -81,9 +75,7 @@
         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:paddingTop="1sp"
         android:showRelative="true"
         android:singleLine="true"
         android:visibility="gone"
@@ -93,7 +85,6 @@
         android:id="@+id/chronometer"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
         android:layout="@layout/notification_template_part_chronometer"
         android:visibility="gone"
@@ -107,8 +98,6 @@
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
-        android:layout_gravity="center"
-        android:paddingTop="1sp"
         android:singleLine="true"
         android:visibility="gone"
         />
@@ -117,9 +106,8 @@
         android:id="@+id/verification_icon"
         android:layout_width="@dimen/notification_verification_icon_size"
         android:layout_height="@dimen/notification_verification_icon_size"
-        android:layout_gravity="center"
         android:layout_marginStart="4dp"
-        android:paddingTop="2dp"
+        android:baseline="10dp"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_notifications_alerted"
         android:visibility="gone"
@@ -130,9 +118,7 @@
         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center"
         android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:paddingTop="1sp"
         android:showRelative="true"
         android:singleLine="true"
         android:visibility="gone"
@@ -142,11 +128,10 @@
         android:id="@+id/feedback"
         android:layout_width="@dimen/notification_feedback_size"
         android:layout_height="@dimen/notification_feedback_size"
-        android:layout_gravity="center"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:background="?android:selectableItemBackgroundBorderless"
         android:contentDescription="@string/notification_feedback_indicator"
-        android:paddingTop="2dp"
+        android:baseline="13dp"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_feedback_indicator"
         android:visibility="gone"
@@ -157,21 +142,19 @@
         android:layout_width="@dimen/notification_phishing_alert_size"
         android:layout_height="@dimen/notification_phishing_alert_size"
         android:layout_marginStart="4dp"
-        android:paddingTop="2dp"
+        android:baseline="10dp"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_dialog_alert_material"
         android:visibility="gone"
         android:contentDescription="@string/notification_phishing_alert_content_description"
         />
 
-
     <ImageView
         android:id="@+id/profile_badge"
         android:layout_width="@dimen/notification_badge_size"
         android:layout_height="@dimen/notification_badge_size"
-        android:layout_gravity="center"
         android:layout_marginStart="4dp"
-        android:paddingTop="2dp"
+        android:baseline="10dp"
         android:scaleType="fitCenter"
         android:visibility="gone"
         android:contentDescription="@string/notification_work_profile_content_description"
@@ -181,10 +164,9 @@
         android:id="@+id/alerted_icon"
         android:layout_width="@dimen/notification_alerted_size"
         android:layout_height="@dimen/notification_alerted_size"
-        android:layout_gravity="center"
         android:layout_marginStart="4dp"
+        android:baseline="10dp"
         android:contentDescription="@string/notification_alerted_content_description"
-        android:paddingTop="2dp"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_notifications_alerted"
         android:visibility="gone"
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
index e9ec7ce..a88ff0d 100644
--- a/core/res/res/layout/notification_template_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -23,8 +23,8 @@
     android:gravity="start|top"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
+    android:paddingTop="20dp"
+    android:paddingBottom="16dp"
     android:importantForAccessibility="no"
     >
 
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 5d9e761..1b3bd26 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -29,7 +29,7 @@
     <LinearLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="80dp"
+        android:layout_height="88dp"
         android:orientation="horizontal"
         >
 
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 88bcc4d..8284279 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -113,11 +113,10 @@
         android:layout_width="@dimen/notification_feedback_size"
         android:layout_height="@dimen/notification_feedback_size"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
-        android:layout_gravity="center"
+        android:baseline="13dp"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_feedback_indicator"
         android:background="?android:selectableItemBackgroundBorderless"
-        android:paddingTop="2dp"
         android:visibility="gone"
         android:contentDescription="@string/notification_feedback_indicator"
         />
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 906a740..f24d663 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1772,6 +1772,9 @@
         <!-- Add algorithm here -->
     </string-array>
 
+    <!-- Boolean indicating if placing the phone face down will result in a screen off. -->
+    <bool name="config_flipToScreenOffEnabled">true</bool>
+
     <!-- Boolean indicating if current platform supports bluetooth SCO for off call
     use cases -->
     <bool name="config_bluetooth_sco_off_call">true</bool>
@@ -4697,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/dimens.xml b/core/res/res/values/dimens.xml
index 2bdf91f..0e436e3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -758,23 +758,23 @@
     <dimen name="notification_grayscale_icon_max_size">256dp</dimen>
 
     <dimen name="messaging_avatar_size">36dp</dimen>
-    <dimen name="conversation_avatar_size">52dp</dimen>
+    <dimen name="conversation_avatar_size">48dp</dimen>
     <!-- start margin of the icon circle in the conversation's skin of the header -->
     <dimen name="conversation_icon_circle_start">28dp</dimen>
     <!-- Start of the content in the conversation template -->
     <dimen name="conversation_content_start">80dp</dimen>
     <!-- Height of the expand button in the conversation layout -->
-    <dimen name="conversation_expand_button_height">80dp</dimen>
+    <dimen name="conversation_expand_button_height">88dp</dimen>
     <!-- this is the margin between the Conversation image and the content -->
     <dimen name="conversation_image_start_margin">12dp</dimen>
     <!-- Side margins of the conversation badge in relation to the conversation icon -->
-    <dimen name="conversation_badge_side_margin">36dp</dimen>
+    <dimen name="conversation_badge_side_margin">32dp</dimen>
     <!-- size of the notification badge when applied to the conversation icon -->
     <dimen name="conversation_icon_size_badged">20dp</dimen>
     <!-- size of the conversation avatar in an expanded group -->
     <dimen name="conversation_avatar_size_group_expanded">@dimen/messaging_avatar_size</dimen>
     <!-- size of the face pile icons -->
-    <dimen name="conversation_face_pile_avatar_size">@dimen/messaging_avatar_size</dimen>
+    <dimen name="conversation_face_pile_avatar_size">32dp</dimen>
     <!-- size of the face pile icons when the group is expanded -->
     <dimen name="conversation_face_pile_avatar_size_group_expanded">25dp</dimen>
     <!-- Side margins of the conversation badge in relation to the conversation icon when the group is expanded-->
@@ -795,7 +795,7 @@
     <dimen name="importance_ring_size">20dp</dimen>
 
     <!-- The top padding of the conversation icon container in the regular state-->
-    <dimen name="conversation_icon_container_top_padding">12dp</dimen>
+    <dimen name="conversation_icon_container_top_padding">20dp</dimen>
 
     <!-- The top padding of the conversation icon container when the avatar is small-->
     <dimen name="conversation_icon_container_top_padding_small_avatar">9dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 90c0691d..5715fab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,7 @@
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
   <java-symbol type="bool" name="config_avoidGfxAccel" />
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
+  <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
   <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
   <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
@@ -4185,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" />
 
@@ -4339,4 +4341,6 @@
   <java-symbol type="drawable" name="ic_accessibility_24dp" />
   <java-symbol type="string" name="view_and_control_notification_title" />
   <java-symbol type="string" name="view_and_control_notification_content" />
+
+  <java-symbol type="layout" name="notification_expand_button"/>
 </resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml
new file mode 100644
index 0000000..1f57318
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.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="#d14d2c">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11.17,19.5h-0.83l0.82,-5.83 -4.18,0.01 5.85,-9.17h0.83l-0.84,5.84h4.17l-5.82,9.15z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml
new file mode 100644
index 0000000..70aac32
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#269e5c">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V5h14V19z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6.25,7.72h5v1.5h-5z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M13,15.75h5v1.5h-5z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M13,13.25h5v1.5h-5z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M8,18l1.5,0l0,-2l2,0l0,-1.5l-2,0l0,-2l-1.5,0l0,2l-2,0l0,1.5l2,0z"/>
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M14.09,10.95l1.41,-1.41l1.41,1.41l1.06,-1.06l-1.41,-1.42l1.41,-1.41l-1.06,-1.06l-1.41,1.41l-1.41,-1.41l-1.06,1.06l1.41,1.41l-1.41,1.42z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml
new file mode 100644
index 0000000..39f9689
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.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="#d14d2c">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M22,9L22,7h-2L20,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2v-2h-2L20,9h2zM18,19L4,19L4,5h14v14zM6,13h5v4L6,17v-4zM12,7h4v3h-4L12,7zM6,7h5v5L6,12L6,7zM12,11h4v6h-4v-6z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.xml
new file mode 100644
index 0000000..9cae545
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.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="M15,3L9,3L9,1h6v2zM11,14h2L13,8h-2v6zM21,13.01c0,4.97 -4.02,9 -9,9s-9,-4.03 -9,-9 4.03,-9 9,-9c2.12,0 4.07,0.74 5.62,1.98l1.42,-1.42c0.51,0.42 0.98,0.9 1.41,1.41L19.03,7.4C20.26,8.93 21,10.89 21,13.01zM19,13.01c0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7 3.13,7 7,7 7,-3.13 7,-7z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
index 1ced825..98fc581 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
@@ -25,6 +25,13 @@
     android:paddingTop="8dp"
     android:paddingBottom="8dp">
 
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="8dp"/>
+
     <TextView
         android:id="@+id/title"
         android:layout_width="0dp"
@@ -34,16 +41,18 @@
 
     <TextView
         android:id="@+id/amount"
-        android:layout_width="0dp"
-        android:layout_weight="0.7"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
         android:gravity="right"
+        android:maxLines="1"
         android:textAppearance="@style/TextAppearanceBody"/>
 
     <TextView
         android:id="@+id/percent"
-        android:layout_width="64dp"
+        android:layout_width="76dp"
         android:layout_height="wrap_content"
         android:gravity="right"
+        android:maxLines="1"
         android:textAppearance="@style/TextAppearanceBody"/>
 </LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index e58a08f..24d193c4 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -43,10 +43,40 @@
                 android:paddingEnd="10dp">
 
                 <include layout="@layout/battery_consumer_info_layout"/>
-
             </LinearLayout>
+
         </androidx.cardview.widget.CardView>
 
+
+        <LinearLayout
+            android:id="@+id/headings"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dp"
+            android:paddingBottom="4dp">
+            <FrameLayout
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"/>
+            <TextView
+                android:layout_width="100dp"
+                android:layout_height="wrap_content"
+                android:gravity="end"
+                android:paddingEnd="10dp"
+                android:text="Total"/>
+            <TextView
+                android:layout_width="100dp"
+                android:layout_height="wrap_content"
+                android:gravity="end"
+                android:paddingEnd="30dp"
+                android:text="Apps"/>
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@android:color/darker_gray"/>
+
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/battery_consumer_data_view"
             android:layout_width="match_parent"
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
new file mode 100644
index 0000000..2dbe48b
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<resources>
+    <color name="battery_consumer_bg_power_profile">#ffffff</color>
+    <color name="battery_consumer_bg_measured_energy">#fff5eb</color>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index 78569bd..f7d7098 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -18,32 +18,21 @@
 
 import android.content.Context;
 import android.os.BatteryConsumer;
-import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
-import android.os.Process;
 import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
-
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
+import android.util.DebugUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class BatteryConsumerData {
-    private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
-    private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
-    private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
-    private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
-            PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
-
-    // Unit conversion:
-    //   mAh = uC * (1/1000)(milli/micro) * (1/3600)(hours/second)
-    private static final double UC_2_MAH = (1.0 / 1000)  * (1.0 / 3600);
 
     enum EntryType {
-        POWER,
+        POWER_MODELED,
+        POWER_MEASURED,
+        POWER_CUSTOM,
         DURATION,
     }
 
@@ -52,268 +41,155 @@
         public EntryType entryType;
         public double value;
         public double total;
+        public boolean isSystemBatteryConsumer;
     }
 
     private final BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo;
     private final List<Entry> mEntries = new ArrayList<>();
 
-    public BatteryConsumerData(Context context, BatteryStatsHelper batteryStatsHelper,
+    public BatteryConsumerData(Context context,
             List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
         BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
-        BatteryUsageStats powerProfileModeledUsageStats = batteryUsageStatsList.get(1);
-        List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
-        BatteryStats batteryStats = batteryStatsHelper.getStats();
+        BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
 
-        double totalPowerMah = 0;
-        double totalSmearedPowerMah = 0;
-        double totalPowerExcludeSystemMah = 0;
-        double totalScreenPower = 0;
-        double totalProportionalSmearMah = 0;
-        double totalCpuPowerMah = 0;
-        double totalSystemServiceCpuPowerMah = 0;
-        double totalUsagePowerMah = 0;
-        double totalWakeLockPowerMah = 0;
-        double totalMobileRadioPowerMah = 0;
-        double totalWifiPowerMah = 0;
-        double totalBluetoothPowerMah = 0;
-        double totalGpsPowerMah = 0;
-        double totalCameraPowerMah = 0;
-        double totalFlashlightPowerMah = 0;
-        double totalSensorPowerMah = 0;
-        double totalAudioPowerMah = 0;
-        double totalVideoPowerMah = 0;
+        BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats,
+                batteryConsumerId);
+        BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer(
+                modeledBatteryUsageStats, batteryConsumerId);
 
-        long totalCpuTimeMs = 0;
-        long totalCpuFgTimeMs = 0;
-        long totalWakeLockTimeMs = 0;
-        long totalWifiRunningTimeMs = 0;
-        long totalBluetoothRunningTimeMs = 0;
-        long totalGpsTimeMs = 0;
-        long totalCameraTimeMs = 0;
-        long totalFlashlightTimeMs = 0;
-        long totalAudioTimeMs = 0;
-        long totalVideoTimeMs = 0;
-
-        BatterySipper requestedBatterySipper = null;
-        for (BatterySipper sipper : usageList) {
-            if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
-                totalScreenPower = sipper.sumPower();
-            }
-
-            if (batteryConsumerId(sipper).equals(batteryConsumerId)) {
-                requestedBatterySipper = sipper;
-            }
-
-            totalPowerMah += sipper.sumPower();
-            totalSmearedPowerMah += sipper.totalSmearedPowerMah;
-            totalProportionalSmearMah += sipper.proportionalSmearMah;
-
-            if (!isSystemSipper(sipper)) {
-                totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
-            }
-
-            totalCpuPowerMah += sipper.cpuPowerMah;
-            totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
-            totalUsagePowerMah += sipper.usagePowerMah;
-            totalWakeLockPowerMah += sipper.wakeLockPowerMah;
-            totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
-            totalWifiPowerMah += sipper.wifiPowerMah;
-            totalBluetoothPowerMah += sipper.bluetoothPowerMah;
-            totalGpsPowerMah += sipper.gpsPowerMah;
-            totalCameraPowerMah += sipper.cameraPowerMah;
-            totalFlashlightPowerMah += sipper.flashlightPowerMah;
-            totalSensorPowerMah += sipper.sensorPowerMah;
-            totalAudioPowerMah += sipper.audioPowerMah;
-            totalVideoPowerMah += sipper.videoPowerMah;
-
-            totalCpuTimeMs += sipper.cpuTimeMs;
-            totalCpuFgTimeMs += sipper.cpuFgTimeMs;
-            totalWakeLockTimeMs += sipper.wakeLockTimeMs;
-            totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
-            totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
-            totalGpsTimeMs += sipper.gpsTimeMs;
-            totalCameraTimeMs += sipper.cameraTimeMs;
-            totalFlashlightTimeMs += sipper.flashlightTimeMs;
-            totalAudioTimeMs += sipper.audioTimeMs;
-            totalVideoTimeMs += sipper.videoTimeMs;
-        }
-
-        BatteryConsumer requestedBatteryConsumer = null;
-
-        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
-            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
-                requestedBatteryConsumer = consumer;
-            }
-        }
-
-        double totalModeledCpuPowerMah = 0;
-        BatteryConsumer requestedBatteryConsumerPowerProfileModeled = null;
-        for (BatteryConsumer consumer : powerProfileModeledUsageStats.getUidBatteryConsumers()) {
-            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
-                requestedBatteryConsumerPowerProfileModeled = consumer;
-            }
-
-            totalModeledCpuPowerMah += consumer.getConsumedPower(
-                    BatteryConsumer.POWER_COMPONENT_CPU);
-        }
-
-        if (requestedBatterySipper == null) {
+        if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) {
             mBatteryConsumerInfo = null;
             return;
         }
 
-        if (requestedBatteryConsumer == null) {
-            for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
-                if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
-                    requestedBatteryConsumer = consumer;
-                    break;
-                }
-            }
-        }
-
         mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
-                context.getPackageManager(), requestedBatterySipper);
-        long totalScreenMeasuredChargeUC =
-                batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
-        long uidScreenMeasuredChargeUC =
-                requestedBatterySipper.uidObj.getScreenOnMeasuredBatteryConsumptionUC();
+                context.getPackageManager(), requestedBatteryConsumer);
 
-        addEntry("Total power", EntryType.POWER,
-                requestedBatterySipper.totalSmearedPowerMah, totalSmearedPowerMah);
-        maybeAddMeasuredEnergyEntry(requestedBatterySipper.drainType, batteryStats);
+        double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
+        double[] totalModeledPowerByComponentMah =
+                new double[BatteryConsumer.POWER_COMPONENT_COUNT];
+        long[] totalDurationByComponentMs = new long[BatteryConsumer.TIME_COMPONENT_COUNT];
+        final int customComponentCount =
+                requestedBatteryConsumer.getCustomPowerComponentCount();
+        double[] totalCustomPowerByComponentMah = new double[customComponentCount];
 
-        addEntry("... excluding system", EntryType.POWER,
-                requestedBatterySipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
-        addEntry("Screen, smeared", EntryType.POWER,
-                requestedBatterySipper.screenPowerMah, totalScreenPower);
-        if (uidScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE
-                && totalScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
-            final double measuredCharge = UC_2_MAH * uidScreenMeasuredChargeUC;
-            final double totalMeasuredCharge = UC_2_MAH * totalScreenMeasuredChargeUC;
-            addEntry("Screen, measured", EntryType.POWER,
-                    measuredCharge, totalMeasuredCharge);
-        }
-        addEntry("Other, smeared", EntryType.POWER,
-                requestedBatterySipper.proportionalSmearMah, totalProportionalSmearMah);
-        addEntry("Excluding smeared", EntryType.POWER,
-                requestedBatterySipper.totalPowerMah, totalPowerMah);
-        if (requestedBatteryConsumer != null) {
-            addEntry("CPU", EntryType.POWER,
-                    requestedBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU),
-                    totalCpuPowerMah);
-            if (requestedBatteryConsumerPowerProfileModeled != null) {
-                addEntry("CPU (modeled)", EntryType.POWER,
-                        requestedBatteryConsumerPowerProfileModeled.getConsumedPower(
-                                BatteryConsumer.POWER_COMPONENT_CPU),
-                        totalModeledCpuPowerMah);
-            }
-        } else {
-            addEntry("CPU (sipper)", EntryType.POWER,
-                    requestedBatterySipper.cpuPowerMah, totalCpuPowerMah);
-        }
-        addEntry("System services", EntryType.POWER,
-                requestedBatterySipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
-        if (requestedBatteryConsumer != null) {
-            addEntry("Usage", EntryType.POWER,
-                    requestedBatteryConsumer.getConsumedPower(
-                            BatteryConsumer.POWER_COMPONENT_USAGE), totalUsagePowerMah);
-        } else {
-            addEntry("Usage (sipper)", EntryType.POWER,
-                    requestedBatterySipper.usagePowerMah, totalUsagePowerMah);
-        }
-        addEntry("Wake lock", EntryType.POWER,
-                requestedBatterySipper.wakeLockPowerMah, totalWakeLockPowerMah);
-        addEntry("Mobile radio", EntryType.POWER,
-                requestedBatterySipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
-        addEntry("WiFi", EntryType.POWER,
-                requestedBatterySipper.wifiPowerMah, totalWifiPowerMah);
-        addEntry("Bluetooth", EntryType.POWER,
-                requestedBatterySipper.bluetoothPowerMah, totalBluetoothPowerMah);
-        addEntry("GPS", EntryType.POWER,
-                requestedBatterySipper.gpsPowerMah, totalGpsPowerMah);
-        addEntry("Camera", EntryType.POWER,
-                requestedBatterySipper.cameraPowerMah, totalCameraPowerMah);
-        addEntry("Flashlight", EntryType.POWER,
-                requestedBatterySipper.flashlightPowerMah, totalFlashlightPowerMah);
-        addEntry("Sensors", EntryType.POWER,
-                requestedBatterySipper.sensorPowerMah, totalSensorPowerMah);
-        addEntry("Audio", EntryType.POWER,
-                requestedBatterySipper.audioPowerMah, totalAudioPowerMah);
-        addEntry("Video", EntryType.POWER,
-                requestedBatterySipper.videoPowerMah, totalVideoPowerMah);
+        computeTotalPower(batteryUsageStats, totalPowerByComponentMah);
+        computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah);
+        computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah);
+        computeTotalDuration(batteryUsageStats, totalDurationByComponentMs);
 
-        addEntry("CPU time", EntryType.DURATION,
-                requestedBatterySipper.cpuTimeMs, totalCpuTimeMs);
-        addEntry("CPU foreground time", EntryType.DURATION,
-                requestedBatterySipper.cpuFgTimeMs, totalCpuFgTimeMs);
-        addEntry("Wake lock time", EntryType.DURATION,
-                requestedBatterySipper.wakeLockTimeMs, totalWakeLockTimeMs);
-        addEntry("WiFi running time", EntryType.DURATION,
-                requestedBatterySipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
-        addEntry("Bluetooth time", EntryType.DURATION,
-                requestedBatterySipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
-        addEntry("GPS time", EntryType.DURATION,
-                requestedBatterySipper.gpsTimeMs, totalGpsTimeMs);
-        addEntry("Camera time", EntryType.DURATION,
-                requestedBatterySipper.cameraTimeMs, totalCameraTimeMs);
-        addEntry("Flashlight time", EntryType.DURATION,
-                requestedBatterySipper.flashlightTimeMs, totalFlashlightTimeMs);
-        addEntry("Audio time", EntryType.DURATION,
-                requestedBatterySipper.audioTimeMs, totalAudioTimeMs);
-        addEntry("Video time", EntryType.DURATION,
-                requestedBatterySipper.videoTimeMs, totalVideoTimeMs);
-    }
-
-    private boolean isSystemSipper(BatterySipper sipper) {
-        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
-        if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
-            return true;
-        } else if (sipper.mPackages != null) {
-            for (final String packageName : sipper.mPackages) {
-                for (final String systemPackage : PACKAGES_SYSTEM) {
-                    if (systemPackage.equals(packageName)) {
-                        return true;
-                    }
-                }
+        for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+            final String metricTitle = getPowerMetricTitle(component);
+            final int powerModel = requestedBatteryConsumer.getPowerModel(component);
+            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
+                addEntry(metricTitle, EntryType.POWER_MODELED,
+                        requestedBatteryConsumer.getConsumedPower(component),
+                        totalPowerByComponentMah[component],
+                        mBatteryConsumerInfo.isSystemBatteryConsumer);
+            } else {
+                addEntry(metricTitle + " (measured)", EntryType.POWER_MEASURED,
+                        requestedBatteryConsumer.getConsumedPower(component),
+                        totalPowerByComponentMah[component],
+                        mBatteryConsumerInfo.isSystemBatteryConsumer);
+                addEntry(metricTitle + " (modeled)", EntryType.POWER_MODELED,
+                        requestedModeledBatteryConsumer.getConsumedPower(component),
+                        totalModeledPowerByComponentMah[component],
+                        mBatteryConsumerInfo.isSystemBatteryConsumer);
             }
         }
 
-        return false;
+        for (int component = 0; component < customComponentCount; component++) {
+            final String name = requestedBatteryConsumer.getCustomPowerComponentName(
+                    BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
+            addEntry(name + " (custom)", EntryType.POWER_CUSTOM,
+                    requestedBatteryConsumer.getConsumedPowerForCustomComponent(
+                            BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
+                    totalCustomPowerByComponentMah[component],
+                    mBatteryConsumerInfo.isSystemBatteryConsumer);
+        }
+
+        for (int component = 0; component < BatteryConsumer.TIME_COMPONENT_COUNT; component++) {
+            final String metricTitle = getTimeMetricTitle(component);
+            addEntry(metricTitle, EntryType.DURATION,
+                    requestedBatteryConsumer.getUsageDurationMillis(component),
+                    totalDurationByComponentMs[component],
+                    mBatteryConsumerInfo.isSystemBatteryConsumer);
+        }
     }
 
-    private void addEntry(String title, EntryType entryType, double amount, double totalAmount) {
+    private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
+            String batteryConsumerId) {
+        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+                return consumer;
+            }
+        }
+        for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
+            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+                return consumer;
+            }
+        }
+        return null;
+    }
+
+    static String getPowerMetricTitle(int componentId) {
+        final String componentName = DebugUtils.constantToString(BatteryConsumer.class,
+                "POWER_COMPONENT_", componentId);
+        return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ')
+                + " power";
+    }
+
+    static String getTimeMetricTitle(int componentId) {
+        final String componentName = DebugUtils.constantToString(BatteryConsumer.class,
+                "TIME_COMPONENT_", componentId);
+        return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ')
+                + " time";
+    }
+
+    private void computeTotalPower(BatteryUsageStats batteryUsageStats,
+            double[] powerByComponentMah) {
+        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+            for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT;
+                    component++) {
+                powerByComponentMah[component] += consumer.getConsumedPower(component);
+            }
+        }
+    }
+
+    private void computeTotalDuration(BatteryUsageStats batteryUsageStats,
+            long[] durationByComponentMs) {
+        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+            for (int component = 0; component < BatteryConsumer.TIME_COMPONENT_COUNT;
+                    component++) {
+                durationByComponentMs[component] += consumer.getUsageDurationMillis(component);
+            }
+        }
+    }
+
+    private void computeTotalPowerForCustomComponent(
+            BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
+        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+            final int customComponentCount = consumer.getCustomPowerComponentCount();
+            for (int component = 0;
+                    component < Math.min(customComponentCount, powerByComponentMah.length);
+                    component++) {
+                powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
+                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
+            }
+        }
+    }
+
+    private void addEntry(String title, EntryType entryType, double amount, double totalAmount,
+            boolean isSystemBatteryConsumer) {
         Entry entry = new Entry();
         entry.title = title;
         entry.entryType = entryType;
         entry.value = amount;
         entry.total = totalAmount;
+        entry.isSystemBatteryConsumer = isSystemBatteryConsumer;
         mEntries.add(entry);
     }
 
-    private void maybeAddMeasuredEnergyEntry(BatterySipper.DrainType drainType,
-            BatteryStats batteryStats) {
-        switch (drainType) {
-            case AMBIENT_DISPLAY:
-                final long totalDozeMeasuredChargeUC =
-                        batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
-                if (totalDozeMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
-                    final double measuredCharge = UC_2_MAH * totalDozeMeasuredChargeUC;
-                    addEntry("Measured ambient display power", EntryType.POWER, measuredCharge,
-                            measuredCharge);
-                }
-                break;
-            case SCREEN:
-                final long totalScreenMeasuredChargeUC =
-                        batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
-                if (totalScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
-                    final double measuredCharge = UC_2_MAH * totalScreenMeasuredChargeUC;
-                    addEntry("Measured screen power", EntryType.POWER, measuredCharge,
-                            measuredCharge);
-                }
-                break;
-        }
-    }
-
     public BatteryConsumerInfoHelper.BatteryConsumerInfo getBatteryConsumerInfo() {
         return mBatteryConsumerInfo;
     }
@@ -322,13 +198,9 @@
         return mEntries;
     }
 
-    public static String batteryConsumerId(BatterySipper sipper) {
-        return sipper.drainType + "|" + sipper.userId + "|" + sipper.getUid();
-    }
-
     public static String batteryConsumerId(BatteryConsumer consumer) {
         if (consumer instanceof UidBatteryConsumer) {
-            return BatterySipper.DrainType.APP + "|"
+            return "APP|"
                     + UserHandle.getUserId(((UidBatteryConsumer) consumer).getUid()) + "|"
                     + ((UidBatteryConsumer) consumer).getUid();
         } else if (consumer instanceof SystemBatteryConsumer) {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index 8ee6c604..6288e0b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -18,14 +18,14 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.BatteryConsumer;
 import android.os.Process;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
+import android.util.DebugUtils;
 
 import androidx.annotation.NonNull;
 
-import com.android.internal.os.BatterySipper;
-
-import java.util.Locale;
-
 class BatteryConsumerInfoHelper {
 
     private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
@@ -37,111 +37,79 @@
         public ApplicationInfo iconInfo;
         public CharSequence packages;
         public CharSequence details;
+        public boolean isSystemBatteryConsumer;
     }
 
     @NonNull
     public static BatteryConsumerInfo makeBatteryConsumerInfo(PackageManager packageManager,
-            @NonNull BatterySipper sipper) {
+            @NonNull BatteryConsumer batteryConsumer) {
         BatteryConsumerInfo info = new BatteryConsumerInfo();
-        info.id = BatteryConsumerData.batteryConsumerId(sipper);
-        sipper.sumPower();
-        info.powerMah = sipper.totalSmearedPowerMah;
-        switch (sipper.drainType) {
-            case APP: {
-                int uid = sipper.getUid();
-                info.details = String.format("UID: %d", uid);
-                String packageWithHighestDrain = sipper.packageWithHighestDrain;
-                if (uid == Process.ROOT_UID) {
-                    info.label = "<root>";
-                } else {
-                    String[] packages = packageManager.getPackagesForUid(uid);
-                    String primaryPackageName = null;
-                    if (uid == Process.SYSTEM_UID) {
-                        primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
-                    } else if (packages != null) {
-                        for (String name : packages) {
-                            primaryPackageName = name;
-                            if (name.equals(packageWithHighestDrain)) {
-                                break;
-                            }
-                        }
-                    }
+        info.id = BatteryConsumerData.batteryConsumerId(batteryConsumer);
+        info.powerMah = batteryConsumer.getConsumedPower();
 
-                    if (primaryPackageName != null) {
-                        try {
-                            ApplicationInfo applicationInfo =
-                                    packageManager.getApplicationInfo(primaryPackageName, 0);
-                            info.label = applicationInfo.loadLabel(packageManager);
-                            info.iconInfo = applicationInfo;
-                        } catch (PackageManager.NameNotFoundException e) {
-                            info.label = primaryPackageName;
+        if (batteryConsumer instanceof UidBatteryConsumer) {
+            final UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer;
+            int uid = uidBatteryConsumer.getUid();
+            info.details = String.format("UID: %d", uid);
+            String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
+            if (uid == Process.ROOT_UID) {
+                info.label = "<root>";
+            } else {
+                String[] packages = packageManager.getPackagesForUid(uid);
+                String primaryPackageName = null;
+                if (uid == Process.SYSTEM_UID) {
+                    primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
+                } else if (packages != null) {
+                    for (String name : packages) {
+                        primaryPackageName = name;
+                        if (name.equals(packageWithHighestDrain)) {
+                            break;
                         }
-                    } else if (packageWithHighestDrain != null) {
-                        info.label = packageWithHighestDrain;
-                    }
-
-                    if (packages != null && packages.length > 0) {
-                        StringBuilder sb = new StringBuilder();
-                        if (primaryPackageName != null) {
-                            sb.append(primaryPackageName);
-                        }
-                        for (String packageName : packages) {
-                            if (packageName.equals(primaryPackageName)) {
-                                continue;
-                            }
-
-                            if (sb.length() != 0) {
-                                sb.append(", ");
-                            }
-                            sb.append(packageName);
-                        }
-
-                        info.packages = sb;
                     }
                 }
-                break;
+
+                if (primaryPackageName != null) {
+                    try {
+                        ApplicationInfo applicationInfo =
+                                packageManager.getApplicationInfo(primaryPackageName, 0);
+                        info.label = applicationInfo.loadLabel(packageManager);
+                        info.iconInfo = applicationInfo;
+                    } catch (PackageManager.NameNotFoundException e) {
+                        info.label = primaryPackageName;
+                    }
+                } else if (packageWithHighestDrain != null) {
+                    info.label = packageWithHighestDrain;
+                }
+
+                if (packages != null && packages.length > 0) {
+                    StringBuilder sb = new StringBuilder();
+                    if (primaryPackageName != null) {
+                        sb.append(primaryPackageName);
+                    }
+                    for (String packageName : packages) {
+                        if (packageName.equals(primaryPackageName)) {
+                            continue;
+                        }
+
+                        if (sb.length() != 0) {
+                            sb.append(", ");
+                        }
+                        sb.append(packageName);
+                    }
+
+                    info.packages = sb;
+                }
             }
-            case USER:
-                info.label = "User";
-                info.details = String.format(Locale.getDefault(), "User ID: %d", sipper.userId);
-                break;
-            case AMBIENT_DISPLAY:
-                info.label = "Ambient display";
-                break;
-            case BLUETOOTH:
-                info.label = "Bluetooth";
-                break;
-            case CAMERA:
-                info.label = "Camera";
-                break;
-            case CELL:
-                info.label = "Cell";
-                break;
-            case FLASHLIGHT:
-                info.label = "Flashlight";
-                break;
-            case IDLE:
-                info.label = "Idle";
-                break;
-            case MEMORY:
-                info.label = "Memory";
-                break;
-            case OVERCOUNTED:
-                info.label = "Overcounted";
-                break;
-            case PHONE:
-                info.label = "Phone";
-                break;
-            case SCREEN:
-                info.label = "Screen";
-                break;
-            case UNACCOUNTED:
-                info.label = "Unaccounted";
-                break;
-            case WIFI:
-                info.label = "WiFi";
-                break;
+        } else if (batteryConsumer instanceof SystemBatteryConsumer) {
+            final SystemBatteryConsumer systemBatteryConsumer =
+                    (SystemBatteryConsumer) batteryConsumer;
+            final int drainType = systemBatteryConsumer.getDrainType();
+            String name = DebugUtils.constantToString(SystemBatteryConsumer.class, "DRAIN_TYPE_",
+                    drainType);
+            info.label = name.charAt(0) + name.substring(1).toLowerCase().replace('_', ' ');
+            info.isSystemBatteryConsumer = true;
         }
+
         // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
         if (info.iconInfo == null) {
             try {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
index bb11fd5..4922087 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
@@ -18,10 +18,11 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.BatteryStats;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -37,8 +38,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.frameworks.core.batterystatsviewer.BatteryConsumerInfoHelper.BatteryConsumerInfo;
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
 import java.util.ArrayList;
@@ -99,44 +98,39 @@
 
     private static class BatteryConsumerListLoader extends
             AsyncLoaderCompat<List<BatteryConsumerInfo>> {
-        private final BatteryStatsHelper mStatsHelper;
         private final int mPickerType;
-        private final UserManager mUserManager;
+        private final BatteryStatsManager mBatteryStatsManager;
         private final PackageManager mPackageManager;
 
         BatteryConsumerListLoader(Context context, int pickerType) {
             super(context);
-            mUserManager = context.getSystemService(UserManager.class);
-            mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
+            mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
             mPickerType = pickerType;
-            mStatsHelper.create((Bundle) null);
-            mStatsHelper.clearStats();
             mPackageManager = context.getPackageManager();
         }
 
         @Override
         public List<BatteryConsumerInfo> loadInBackground() {
+            final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
+
             List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>();
-
-            mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
-            final List<BatterySipper> usageList = mStatsHelper.getUsageList();
-            for (BatterySipper sipper : usageList) {
-                switch (mPickerType) {
-                    case PICKER_TYPE_APP:
-                        if (sipper.drainType != BatterySipper.DrainType.APP) {
-                            continue;
-                        }
-                        break;
-                    case PICKER_TYPE_DRAIN:
-                    default:
-                        if (sipper.drainType == BatterySipper.DrainType.APP) {
-                            continue;
-                        }
-                }
-
-                batteryConsumerList.add(
-                        BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager, sipper));
+            switch (mPickerType) {
+                case PICKER_TYPE_APP:
+                    for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+                        batteryConsumerList.add(
+                                BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
+                                        consumer));
+                    }
+                    break;
+                case PICKER_TYPE_DRAIN:
+                default:
+                    for (SystemBatteryConsumer consumer :
+                            batteryUsageStats.getSystemBatteryConsumers()) {
+                        batteryConsumerList.add(
+                                BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
+                                        consumer));
+                    }
+                    break;
             }
 
             batteryConsumerList.sort(
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 4ead8ee..74d3fb3 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -18,13 +18,10 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.os.BatteryStats;
 import android.os.BatteryStatsManager;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -41,7 +38,6 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.internal.os.BatteryStatsHelper;
 import com.android.settingslib.utils.AsyncLoaderCompat;
 
 import java.util.Collections;
@@ -50,24 +46,24 @@
 
 public class BatteryStatsViewerActivity extends ComponentActivity {
     private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
-    public static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
-    public static final int LOADER_BATTERY_STATS_HELPER = 0;
-    public static final int LOADER_BATTERY_USAGE_STATS = 1;
+    private static final int MILLIS_IN_MINUTE = 60000;
+    private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
+    private static final int LOADER_BATTERY_USAGE_STATS = 1;
 
     private BatteryStatsDataAdapter mBatteryStatsDataAdapter;
-    private Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
+    private final Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
     private SharedPreferences mSharedPref;
     private String mBatteryConsumerId;
     private TextView mTitleView;
     private TextView mDetailsView;
     private ImageView mIconView;
     private TextView mPackagesView;
+    private View mHeadingsView;
     private RecyclerView mBatteryConsumerDataView;
     private View mLoadingView;
     private View mEmptyView;
-    private ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
+    private final ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
             BatteryConsumerPickerActivity.CONTRACT, this::onApplicationSelected);
-    private BatteryStatsHelper mBatteryStatsHelper;
     private List<BatteryUsageStats> mBatteryUsageStats;
 
     @Override
@@ -85,6 +81,7 @@
         mDetailsView = findViewById(R.id.details);
         mIconView = findViewById(android.R.id.icon);
         mPackagesView = findViewById(R.id.packages);
+        mHeadingsView = findViewById(R.id.headings);
 
         mBatteryConsumerDataView = findViewById(R.id.battery_consumer_data_view);
         mBatteryConsumerDataView.setLayoutManager(new LinearLayoutManager(this));
@@ -139,55 +136,10 @@
 
     private void loadBatteryStats() {
         LoaderManager loaderManager = LoaderManager.getInstance(this);
-        loaderManager.restartLoader(LOADER_BATTERY_STATS_HELPER, null,
-                new BatteryStatsHelperLoaderCallbacks());
         loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
                 new BatteryUsageStatsLoaderCallbacks());
     }
 
-    private static class BatteryStatsHelperLoader extends AsyncLoaderCompat<BatteryStatsHelper> {
-        private final BatteryStatsHelper mBatteryStatsHelper;
-        private final UserManager mUserManager;
-
-        BatteryStatsHelperLoader(Context context) {
-            super(context);
-            mUserManager = context.getSystemService(UserManager.class);
-            mBatteryStatsHelper = new BatteryStatsHelper(context,
-                    false /* collectBatteryBroadcast */);
-            mBatteryStatsHelper.create((Bundle) null);
-            mBatteryStatsHelper.clearStats();
-        }
-
-        @Override
-        public BatteryStatsHelper loadInBackground() {
-            mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
-                    UserHandle.myUserId());
-            return mBatteryStatsHelper;
-        }
-
-        @Override
-        protected void onDiscardResult(BatteryStatsHelper result) {
-        }
-    }
-
-    private class BatteryStatsHelperLoaderCallbacks implements LoaderCallbacks<BatteryStatsHelper> {
-        @NonNull
-        @Override
-        public Loader<BatteryStatsHelper> onCreateLoader(int id, Bundle args) {
-            return new BatteryStatsHelperLoader(BatteryStatsViewerActivity.this);
-        }
-
-        @Override
-        public void onLoadFinished(@NonNull Loader<BatteryStatsHelper> loader,
-                BatteryStatsHelper batteryStatsHelper) {
-            onBatteryStatsHelperLoaded(batteryStatsHelper);
-        }
-
-        @Override
-        public void onLoaderReset(@NonNull Loader<BatteryStatsHelper> loader) {
-        }
-    }
-
     private static class BatteryUsageStatsLoader extends
             AsyncLoaderCompat<List<BatteryUsageStats>> {
         private final BatteryStatsManager mBatteryStatsManager;
@@ -200,10 +152,13 @@
         @Override
         public List<BatteryUsageStats> loadInBackground() {
             final BatteryUsageStatsQuery queryDefault =
-                    new BatteryUsageStatsQuery.Builder().build();
+                    new BatteryUsageStatsQuery.Builder()
+                            .includePowerModels()
+                            .build();
             final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
                     new BatteryUsageStatsQuery.Builder()
                             .powerProfileModeledOnly()
+                            .includePowerModels()
                             .build();
             return mBatteryStatsManager.getBatteryUsageStats(
                     List.of(queryDefault, queryPowerProfileModeledOnly));
@@ -233,22 +188,13 @@
         }
     }
 
-    public void onBatteryStatsHelperLoaded(BatteryStatsHelper batteryStatsHelper) {
-        mBatteryStatsHelper = batteryStatsHelper;
-        onBatteryStatsDataLoaded();
-    }
-
     private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
         mBatteryUsageStats = batteryUsageStats;
         onBatteryStatsDataLoaded();
     }
 
     public void onBatteryStatsDataLoaded() {
-        if (mBatteryStatsHelper == null || mBatteryUsageStats == null) {
-            return;
-        }
-
-        BatteryConsumerData batteryConsumerData = new BatteryConsumerData(this, mBatteryStatsHelper,
+        BatteryConsumerData batteryConsumerData = new BatteryConsumerData(this,
                 mBatteryUsageStats, mBatteryConsumerId);
 
         BatteryConsumerInfoHelper.BatteryConsumerInfo
@@ -256,6 +202,7 @@
         if (batteryConsumerInfo == null) {
             mTitleView.setText("Battery consumer not found");
             mPackagesView.setVisibility(View.GONE);
+            mHeadingsView.setVisibility(View.GONE);
         } else {
             mTitleView.setText(batteryConsumerInfo.label);
             if (batteryConsumerInfo.details != null) {
@@ -273,6 +220,12 @@
             } else {
                 mPackagesView.setVisibility(View.GONE);
             }
+
+            if (batteryConsumerInfo.isSystemBatteryConsumer) {
+                mHeadingsView.setVisibility(View.VISIBLE);
+            } else {
+                mHeadingsView.setVisibility(View.GONE);
+            }
         }
 
         mBatteryStatsDataAdapter.setEntries(batteryConsumerData.getEntries());
@@ -290,6 +243,7 @@
     private static class BatteryStatsDataAdapter extends
             RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> {
         public static class ViewHolder extends RecyclerView.ViewHolder {
+            public ImageView iconImageView;
             public TextView titleTextView;
             public TextView amountTextView;
             public TextView percentTextView;
@@ -297,6 +251,7 @@
             ViewHolder(View itemView) {
                 super(itemView);
 
+                iconImageView = itemView.findViewById(R.id.icon);
                 titleTextView = itemView.findViewById(R.id.title);
                 amountTextView = itemView.findViewById(R.id.amount);
                 percentTextView = itemView.findViewById(R.id.percent);
@@ -328,21 +283,56 @@
         public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
             BatteryConsumerData.Entry entry = mEntries.get(position);
             switch (entry.entryType) {
-                case POWER:
+                case POWER_MODELED:
                     viewHolder.titleTextView.setText(entry.title);
                     viewHolder.amountTextView.setText(
                             String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+                    viewHolder.iconImageView.setImageResource(R.drawable.gm_calculate_24);
+                    viewHolder.itemView.setBackgroundResource(
+                            R.color.battery_consumer_bg_power_profile);
+                    break;
+                case POWER_MEASURED:
+                    viewHolder.titleTextView.setText(entry.title);
+                    viewHolder.amountTextView.setText(
+                            String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+                    viewHolder.iconImageView.setImageResource(R.drawable.gm_amp_24);
+                    viewHolder.itemView.setBackgroundResource(
+                            R.color.battery_consumer_bg_measured_energy);
+                    break;
+                case POWER_CUSTOM:
+                    viewHolder.titleTextView.setText(entry.title);
+                    viewHolder.amountTextView.setText(
+                            String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+                    viewHolder.iconImageView.setImageResource(R.drawable.gm_custom_24);
+                    viewHolder.itemView.setBackgroundResource(
+                            R.color.battery_consumer_bg_measured_energy);
                     break;
                 case DURATION:
                     viewHolder.titleTextView.setText(entry.title);
-                    viewHolder.amountTextView.setText(
-                            String.format(Locale.getDefault(), "%,d ms", (long) entry.value));
+                    final long durationMs = (long) entry.value;
+                    CharSequence text;
+                    if (durationMs < MILLIS_IN_MINUTE) {
+                        text = String.format(Locale.getDefault(), "%,d ms", durationMs);
+                    } else {
+                        text = String.format(Locale.getDefault(), "%,d m %d s",
+                                durationMs / MILLIS_IN_MINUTE,
+                                (durationMs % MILLIS_IN_MINUTE) / 1000);
+                    }
+
+                    viewHolder.amountTextView.setText(text);
+                    viewHolder.iconImageView.setImageResource(R.drawable.gm_timer_24);
+                    viewHolder.itemView.setBackground(null);
                     break;
             }
 
-            double proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
-            viewHolder.percentTextView.setText(String.format(Locale.getDefault(), "%.1f%%",
-                    proportion));
+            double proportion;
+            if (entry.isSystemBatteryConsumer) {
+                proportion = entry.value != 0 ? entry.total * 100 / entry.value : 0;
+            } else {
+                proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
+            }
+            viewHolder.percentTextView.setText(
+                    String.format(Locale.getDefault(), "%.1f%%", proportion));
         }
     }
 }
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/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index cf47efd..c63ec45 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -65,12 +65,12 @@
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(
                         SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
                 .isEqualTo(90 * MINUTE_IN_MS);
         // 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(27.777778);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
@@ -91,11 +91,11 @@
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(
                         SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
                 .isEqualTo(90 * MINUTE_IN_MS);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(15.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 0dd1977..b253599 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -77,7 +77,7 @@
                 .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
                 .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000)
                 .setConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_USAGE, 300)
+                        BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, 400)
                 .setConsumedPowerForCustomComponent(
@@ -123,7 +123,7 @@
                 assertThat(uidBatteryConsumer.getTimeInStateMs(
                         UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000);
                 assertThat(uidBatteryConsumer.getConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300);
+                        BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(300);
                 assertThat(uidBatteryConsumer.getConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400);
                 assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
@@ -157,6 +157,8 @@
                         BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400);
                 assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300);
                 assertThat(systemBatteryConsumer.getPowerConsumedByApps()).isEqualTo(20000);
+                assertThat(systemBatteryConsumer.getUsageDurationMillis())
+                        .isEqualTo(10400); // max
                 assertThat(systemBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
                 assertThat(systemBatteryConsumer.getCustomPowerComponentName(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index f65fb95..bf87683 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -23,16 +25,21 @@
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BinderInternal.CallSession;
 import com.android.internal.os.BinderLatencyObserver.LatencyDims;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Random;
 
@@ -49,11 +56,17 @@
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
+        blo.setElapsedTime(4);
         blo.callEnded(callSession);
+        blo.setElapsedTime(6);
         blo.callEnded(callSession);
         callSession.transactionCode = 2;
+        blo.setElapsedTime(8);
         blo.callEnded(callSession);
+        blo.setElapsedTime(10);
         blo.callEnded(callSession);
 
         ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
@@ -74,8 +87,10 @@
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
         callSession.transactionCode = 2;
+        blo.setElapsedTime(4);
         blo.callEnded(callSession);
 
         ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
@@ -89,13 +104,13 @@
     @Test
     public void testTooCallLengthOverflow() {
         TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
-        blo.setElapsedTime(2L + (long) Integer.MAX_VALUE);
         blo.setHistogramBucketsParams(5, 5, 1.125f);
 
         Binder binder = new Binder();
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+        blo.setElapsedTime(2L + (long) Integer.MAX_VALUE);
         blo.callEnded(callSession);
 
         // The long call should be capped to maxint (to not overflow) and placed in the last bucket.
@@ -114,6 +129,7 @@
         CallSession callSession = new CallSession();
         callSession.binderClass = binder.getClass();
         callSession.transactionCode = 1;
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
 
         LatencyDims dims = new LatencyDims(binder.getClass(), 1);
@@ -122,14 +138,111 @@
         assertThat(blo.getLatencyHistograms().get(dims))
             .asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         // Try to add another sample.
+        blo.setElapsedTime(2);
         blo.callEnded(callSession);
         // Make sure the buckets don't overflow.
         assertThat(blo.getLatencyHistograms().get(dims))
             .asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
     }
 
+    @Test
+    public void testSingleAtomPush() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(7);
+        blo.callEnded(callSession);
+        blo.callEnded(callSession);
+        blo.setElapsedTime(8);
+        blo.callEnded(callSession);
+
+        // Trigger the statsd push.
+        blo.getStatsdPushRunnable().run();
+
+        ProtoOutputStream expectedProto = new ProtoOutputStream();
+        long apiStatsToken = expectedProto.start(RepeatedApiStats.API_STATS);
+        long dimsToken = expectedProto.start(ApiStats.DIMS);
+        expectedProto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto.write(Dims.SERVICE_METHOD_NAME, "1");
+        expectedProto.end(dimsToken);
+        expectedProto.write(ApiStats.FIRST_BUCKET_INDEX, 3);
+        expectedProto.write(ApiStats.BUCKETS, 2);
+        expectedProto.write(ApiStats.BUCKETS, 1);
+        expectedProto.end(apiStatsToken);
+
+        assertThat(blo.getWrittenAtoms())
+                .containsExactly(Arrays.toString(expectedProto.getBytes()));
+    }
+
+    @Test
+    public void testMultipleAtomPush() {
+        TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+        BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+
+
+        Binder binder = new Binder();
+        CallSession callSession = new CallSession();
+        callSession.binderClass = binder.getClass();
+        callSession.transactionCode = 1;
+        blo.setElapsedTime(1);
+        blo.callEnded(callSession);
+        callSession.transactionCode = 2;
+        blo.setElapsedTime(5);
+        blo.callEnded(callSession);
+        callSession.transactionCode = 3;
+        blo.callEnded(callSession);
+
+        // Trigger the statsd push.
+        blo.getStatsdPushRunnable().run();
+
+        ProtoOutputStream expectedProto1 = new ProtoOutputStream();
+        long apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+        long dimsToken = expectedProto1.start(ApiStats.DIMS);
+        expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto1.write(Dims.SERVICE_METHOD_NAME, "1");
+        expectedProto1.end(dimsToken);
+        expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 0);
+        expectedProto1.write(ApiStats.BUCKETS, 1);
+        expectedProto1.end(apiStatsToken);
+
+        apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+        dimsToken = expectedProto1.start(ApiStats.DIMS);
+        expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto1.write(Dims.SERVICE_METHOD_NAME, "2");
+        expectedProto1.end(dimsToken);
+        expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+        expectedProto1.write(ApiStats.BUCKETS, 1);
+        expectedProto1.end(apiStatsToken);
+
+        ProtoOutputStream expectedProto2 = new ProtoOutputStream();
+        apiStatsToken = expectedProto2.start(RepeatedApiStats.API_STATS);
+        dimsToken = expectedProto2.start(ApiStats.DIMS);
+        expectedProto2.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+        expectedProto2.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+        expectedProto2.write(Dims.SERVICE_METHOD_NAME, "3");
+        expectedProto2.end(dimsToken);
+        expectedProto2.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+        expectedProto2.write(ApiStats.BUCKETS, 1);
+        expectedProto2.end(apiStatsToken);
+
+        // Each ApiStats is around ~60 bytes so only two should fit in an atom.
+        assertThat(blo.getWrittenAtoms())
+                .containsExactly(
+                        Arrays.toString(expectedProto1.getBytes()),
+                        Arrays.toString(expectedProto2.getBytes()))
+                .inOrder();
+    }
+
     public static class TestBinderLatencyObserver extends BinderLatencyObserver {
         private long mElapsedTime = 0;
+        private ArrayList<String> mWrittenAtoms;
 
         TestBinderLatencyObserver() {
             // Make random generator not random.
@@ -145,16 +258,30 @@
                 }
             });
             setSamplingInterval(1);
+            mWrittenAtoms = new ArrayList<>();
         }
 
         @Override
         protected long getElapsedRealtimeMicro() {
-            mElapsedTime += 2;
             return mElapsedTime;
         }
 
+        @Override
+        protected int getMaxAtomSizeBytes() {
+            return 1100;
+        }
+
+        @Override
+        protected void writeAtomToStatsd(ProtoOutputStream atom) {
+            mWrittenAtoms.add(Arrays.toString(atom.getBytes()));
+        }
+
         public void setElapsedTime(long time) {
             mElapsedTime = time;
         }
+
+        public ArrayList<String> getWrittenAtoms() {
+            return mWrittenAtoms;
+        }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 781e725..a9800b7 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -48,9 +48,9 @@
 
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_IDLE);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_IDLE))
                 .isEqualTo(3000);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
                 .isWithin(PRECISION).of(0.7);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
index 8f21503..71dbcdb 100644
--- a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
@@ -55,9 +55,9 @@
 
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MEMORY);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MEMORY))
                 .isEqualTo(3000);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY))
                 .isWithin(PRECISION).of(0.7);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 9cd6ea8..7d829e4 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -84,13 +84,13 @@
 
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
                 .isEqualTo(80 * MINUTE_IN_MS);
 
         // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s)  = 166.66666 mAh
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(166.66666);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
         assertThat(consumer.getConsumedPower())
                 .isWithin(PRECISION).of(166.66666);
@@ -153,11 +153,11 @@
 
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
                 .isEqualTo(80 * MINUTE_IN_MS);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(92.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertThat(consumer.getConsumedPower())
                 .isWithin(PRECISION).of(92.0);
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2fe8b28..5f34426 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -348,6 +348,8 @@
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
         <permission name="android.permission.INSTALL_PACKAGES"/>
         <!-- Needed for test only -->
+        <permission name="android.permission.ACCESS_MTP"/>
+        <!-- Needed for test only -->
         <permission name="android.permission.INTERACT_ACROSS_PROFILES"/>
         <!-- Permission required to test onPermissionsChangedListener -->
         <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
@@ -404,6 +406,7 @@
         <permission name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
         <permission name="android.permission.USE_RESERVED_DISK"/>
+        <permission name="android.permission.UWB_PRIVILEGED"/>
         <permission name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
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/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 11c1464..dca5985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -739,14 +739,11 @@
         return (isSummary && isSuppressedSummary) || isSuppressedBubble;
     }
 
-    private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
-            Executor callbackExecutor) {
+    private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) {
         if (mBubbleData.isSummarySuppressed(groupKey)) {
             mBubbleData.removeSuppressedSummary(groupKey);
             if (callback != null) {
-                callbackExecutor.execute(() -> {
-                    callback.accept(mBubbleData.getSummaryKey(groupKey));
-                });
+                callback.accept(mBubbleData.getSummaryKey(groupKey));
             }
         }
     }
@@ -1298,8 +1295,10 @@
         public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
                 Executor callbackExecutor) {
             mMainExecutor.execute(() -> {
-                BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, callback,
-                        callbackExecutor);
+                Consumer<String> cb = callback != null
+                        ? (key) -> callbackExecutor.execute(() -> callback.accept(key))
+                        : null;
+                BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb);
             });
         }
 
@@ -1340,10 +1339,13 @@
 
         @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
-                @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
+                @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
+                Executor callbackExecutor) {
+            IntConsumer cb = removeCallback != null
+                    ? (index) -> callbackExecutor.execute(() -> removeCallback.accept(index))
+                    : null;
             return mMainExecutor.executeBlockingForResult(() -> {
-                return BubbleController.this.handleDismissalInterception(entry, children,
-                        removeCallback);
+                return BubbleController.this.handleDismissalInterception(entry, children, cb);
             }, Boolean.class);
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 9fc8aef..1bfb619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -140,7 +140,7 @@
      * @return true if we want to intercept the dismissal of the entry, else false.
      */
     boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
-            IntConsumer removeCallback);
+            IntConsumer removeCallback, Executor callbackExecutor);
 
     /** Set the proxy to commnuicate with SysUi side components. */
     void setSysuiProxy(SysuiProxy proxy);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 5c3af3e..8ac9a7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -37,6 +37,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Controller class of PiP animations (both from and to PiP mode).
@@ -112,6 +113,7 @@
                     PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
                             alphaEnd));
         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
+                && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds())
                 && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.updateEndValue(alphaEnd);
         } else {
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/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e152633..e66be66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -99,9 +99,10 @@
     private enum State {
         UNDEFINED(0),
         TASK_APPEARED(1),
-        ENTERING_PIP(2),
-        ENTERED_PIP(3),
-        EXITING_PIP(4);
+        ENTRY_SCHEDULED(2),
+        ENTERING_PIP(3),
+        ENTERED_PIP(4),
+        EXITING_PIP(5);
 
         private final int mStateValue;
 
@@ -265,6 +266,13 @@
     }
 
     /**
+     * Returns whether the entry animation is waiting to be started.
+     */
+    public boolean isEntryScheduled() {
+        return mState == State.ENTRY_SCHEDULED;
+    }
+
+    /**
      * Registers a callback when a display change has been detected when we enter PiP.
      */
     public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
@@ -492,6 +500,19 @@
         }
     }
 
+    /**
+     * Called when the display rotation handling is skipped (e.g. when rotation happens while in
+     * the middle of an entry transition).
+     */
+    public void onDisplayRotationSkipped() {
+        if (isEntryScheduled()) {
+            // The PIP animation is scheduled to start with the previous orientation's bounds,
+            // re-calculate the entry bounds and restart the alpha animation.
+            final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+            enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
+        }
+    }
+
     @VisibleForTesting
     void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
         // If we are fading the PIP in, then we should move the pip to the final location as
@@ -501,6 +522,7 @@
                 mSurfaceControlTransactionFactory.getTransaction();
         tx.setAlpha(mLeash, 0f);
         tx.apply();
+        mState = State.ENTRY_SCHEDULED;
         applyEnterPipSyncTransaction(destinationBounds, () -> {
             mPipAnimationController
                     .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 62ae1d5..f505e60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -116,7 +116,8 @@
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
         if (!mPipTaskOrganizer.isInPip()
                 || mPipBoundsState.getDisplayLayout().rotation() == toRotation
-                || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
+                || mPipTaskOrganizer.isDeferringEnterPipAnimation()
+                || mPipTaskOrganizer.isEntryScheduled()) {
             // Skip if the same rotation has been set or we aren't in PIP or haven't actually
             // entered PIP yet. We still need to update the display layout in the bounds handler
             // in this case.
@@ -124,6 +125,7 @@
             // do not forget to update the movement bounds as well.
             updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */,
                     false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
+            mPipTaskOrganizer.onDisplayRotationSkipped();
             return;
         }
         // If there is an animation running (ie. from a shelf offset), then ensure that we calculate
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/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/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 864350e..3a19b13 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -2495,7 +2495,10 @@
      * The default security level is defined as the highest security level
      * supported on the device.
      *
-     * @param mime The mime type of the media data
+     * @param mime The mime type of the media data. Please use {@link
+     *             #isCryptoSchemeSupported(UUID, String)} to query mime type support separately;
+     *             for unsupported mime types the return value of {@link
+     *             #requiresSecureDecoder(String)} is crypto scheme dependent.
      */
     public boolean requiresSecureDecoder(@NonNull String mime) {
         return requiresSecureDecoder(mime, getMaxSecurityLevel());
@@ -2505,7 +2508,10 @@
      * Query if the crypto scheme requires the use of a secure decoder
      * to decode data of the given mime type at the given security level.
      *
-     * @param mime The mime type of the media data
+     * @param mime The mime type of the media data. Please use {@link
+     *             #isCryptoSchemeSupported(UUID, String, int)} to query mime type support
+     *             separately; for unsupported mime types the return value of {@link
+     *             #requiresSecureDecoder(String, int)} is crypto scheme dependent.
      * @param level a security level between {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}
      *              and {@link #SECURITY_LEVEL_HW_SECURE_ALL}. Otherwise the special value
      *              {@link #getMaxSecurityLevel()} is also permitted;
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/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/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 63e0fe9..2194575 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -239,6 +239,7 @@
     method public final void sendQosSessionLost(int, int, int);
     method public final void sendSocketKeepaliveEvent(int, int);
     method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void setLingerDuration(@NonNull java.time.Duration);
     method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int);
     method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
     method public void unregister();
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/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index 26cb1ed..9a58add 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -42,4 +42,5 @@
     void sendQosSessionLost(int qosCallbackId, in QosSession session);
     void sendQosCallbackError(int qosCallbackId, int exceptionType);
     void sendTeardownDelayMs(int teardownDelayMs);
+    void sendLingerDuration(int durationMs);
 }
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/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 3622c1c..518d3f3 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -106,6 +107,9 @@
     private final String LOG_TAG;
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
+    /** @hide */
+    @TestApi
+    public static final int MIN_LINGER_TIMER_MS = 2000;
     private final ArrayList<RegistryAction> mPreConnectedQueue = new ArrayList<>();
     private volatile long mLastBwRefreshTime = 0;
     private static final long BW_REFRESH_MIN_WIN_MS = 500;
@@ -391,6 +395,15 @@
      */
     public static final int CMD_NETWORK_DESTROYED = BASE + 23;
 
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to set the linger duration for this network
+     * agent.
+     * arg1 = the linger duration, represents by {@link Duration}.
+     *
+     * @hide
+     */
+    public static final int EVENT_LINGER_DURATION_CHANGED = BASE + 24;
+
     private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
         final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
                 config.legacyTypeName, config.legacySubTypeName);
@@ -1287,6 +1300,22 @@
         queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
     }
 
+    /**
+     * Set the linger duration for this network agent.
+     * @param duration the delay between the moment the network becomes unneeded and the
+     *                 moment the network is disconnected or moved into the background.
+     *                 Note that If this duration has greater than millisecond precision, then
+     *                 the internal implementation will drop any excess precision.
+     */
+    public void setLingerDuration(@NonNull final Duration duration) {
+        Objects.requireNonNull(duration);
+        final long durationMs = duration.toMillis();
+        if (durationMs < MIN_LINGER_TIMER_MS || durationMs > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException("Duration must be within ["
+                    + MIN_LINGER_TIMER_MS + "," + Integer.MAX_VALUE + "]ms");
+        }
+        queueOrSendMessage(ra -> ra.sendLingerDuration((int) durationMs));
+    }
 
     /** @hide */
     protected void log(final String s) {
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/SettingsTheme/res/values/styles_preference.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
index dcbdc07..cec8b32 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
@@ -15,7 +15,7 @@
   limitations under the License.
   -->
 <resources>
-    <!--DEPRECATED. It will remove after all of client team migrated to new style. -->
+    <!--DEPRECATED. It will be removed after all of client teams migrated to new style. -->
     <style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay">
         <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
         <item name="preferenceStyle">@style/SettingsPreference</item>
@@ -28,7 +28,7 @@
         <item name="footerPreferenceStyle">@style/Preference.Material</item>
     </style>
 
-    <style name="SettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay">
+    <style name="PreferenceTheme.SettingsBase" parent="@style/PreferenceThemeOverlay">
         <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
         <item name="preferenceStyle">@style/SettingsPreference</item>
         <item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 13c7523..9c096d2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -21,7 +21,7 @@
         <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle</item>
         <item name="android:listPreferredItemPaddingStart">@dimen/preference_padding_start</item>
         <item name="android:listPreferredItemPaddingEnd">@dimen/preference_padding_end</item>
-        <item name="preferenceTheme">@style/SettingsPreferenceTheme</item>
+        <item name="preferenceTheme">@style/PreferenceTheme.SettingsBase</item>
     </style>
 
     <!-- Using in SubSettings page including injected settings page -->
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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bef6423f..f685d88 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -113,6 +113,8 @@
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <!-- ACCESS_MTP is needed for testing purposes only. -->
+    <uses-permission android:name="android.permission.ACCESS_MTP" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -539,6 +541,9 @@
     <!-- Permission required for CTS test - CtsRotationResolverServiceDeviceTestCases -->
     <uses-permission android:name="android.permission.MANAGE_ROTATION_RESOLVER" />
 
+    <!-- Permission required for CTS test - CtsUwbTestCases -->
+    <uses-permission android:name="android.permission.UWB_PRIVILEGED" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index da78a7c..3363f8e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -313,12 +313,14 @@
             }
 
             context.mainExecutor.execute {
-                startAnimation(remoteAnimationTargets, iRemoteAnimationFinishedCallback)
+                startAnimation(remoteAnimationTargets, remoteAnimationNonAppTargets,
+                        iRemoteAnimationFinishedCallback)
             }
         }
 
         private fun startAnimation(
             remoteAnimationTargets: Array<out RemoteAnimationTarget>,
+            remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
             iCallback: IRemoteAnimationFinishedCallback
         ) {
             val window = remoteAnimationTargets.firstOrNull {
@@ -332,7 +334,7 @@
                 return
             }
 
-            val navigationBar = remoteAnimationTargets.firstOrNull {
+            val navigationBar = remoteAnimationNonAppTargets.firstOrNull {
                 it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
             }
 
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 35423a9..53ff9f0 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -58,5 +58,7 @@
     /** View to which this plugin can be registered, in order to get updates. */
     interface SmartspaceView {
         void registerDataProvider(BcSmartspaceDataPlugin plugin);
+
+        void setPrimaryTextColor(int color);
     }
 }
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 00c27bf..1cef44b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -79,7 +79,6 @@
         android:id="@+id/left_aligned_notification_icon_container"
         android:layout_width="match_parent"
         android:layout_height="@dimen/notification_shelf_height"
-        android:layout_marginTop="@dimen/widget_vertical_padding"
         android:layout_below="@id/keyguard_status_area"
         android:paddingStart="@dimen/below_clock_padding_start"
     />
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/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/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/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 2355650..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>
@@ -656,4 +656,10 @@
         <!-- Y -->
         <!-- radius -->
     </integer-array>
+
+    <!-- Overrides the behavior of the face unlock keyguard bypass setting:
+         0 - Don't override the setting (default)
+         1 - Override the setting to always bypass keyguard
+         2 - Override the setting to never bypass keyguard -->
+    <integer name="config_face_unlock_bypass_override">0</integer>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ad00882..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>
@@ -1415,17 +1414,17 @@
     <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
     <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
 
-    <dimen name="rounded_slider_height">44dp</dimen>
+    <dimen name="rounded_slider_height">48dp</dimen>
     <!-- rounded_slider_height / 2 -->
-    <dimen name="rounded_slider_corner_radius">22dp</dimen>
+    <dimen name="rounded_slider_corner_radius">24dp</dimen>
     <dimen name="rounded_slider_icon_size">20dp</dimen>
     <!-- (rounded_slider_height - rounded_slider_icon_size) / 2 -->
-    <dimen name="rounded_slider_icon_inset">12dp</dimen>
+    <dimen name="rounded_slider_icon_inset">14dp</dimen>
     <!-- rounded_slider_corner_radius - rounded_slider_track_corner_radius -->
-    <dimen name="rounded_slider_track_inset">18dp</dimen>
-    <dimen name="rounded_slider_track_width">8dp</dimen>
+    <dimen name="rounded_slider_track_inset">22dp</dimen>
+    <dimen name="rounded_slider_track_width">4dp</dimen>
     <!-- rounded_slider_track_width / 2 -->
-    <dimen name="rounded_slider_track_corner_radius">4dp</dimen>
+    <dimen name="rounded_slider_track_corner_radius">2dp</dimen>
 
     <!-- inset for ic_lock_open within a DisabledUdfpsView -->
     <dimen name="udfps_unlock_icon_inset">16dp</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/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cb825c0..f89e365 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -26,14 +26,18 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.clock.ClockManager;
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -50,6 +54,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 
 import java.util.Locale;
@@ -87,6 +92,10 @@
     private Executor mUiExecutor;
     private SmartspaceSession mSmartspaceSession;
     private SmartspaceSession.Callback mSmartspaceCallback;
+    private float mDozeAmount;
+    private int mWallpaperTextColor;
+    private int mDozeColor = Color.WHITE;
+    private ConfigurationController mConfigurationController;
 
     /**
      * Listener for changes to the color palette.
@@ -103,8 +112,25 @@
         }
     };
 
+    private final ConfigurationController.ConfigurationListener mConfigurationListener =
+            new ConfigurationController.ConfigurationListener() {
+        @Override
+        public void onThemeChanged() {
+            updateWallpaperColor();
+        }
+    };
+
     private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
 
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onDozeAmountChanged(float linear, float eased) {
+                    mDozeAmount = eased;
+                    updateSmartspaceColor();
+                }
+            };
+
     // If set, will replace keyguard_status_area
     private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
 
@@ -121,7 +147,8 @@
             PluginManager pluginManager,
             FeatureFlags featureFlags,
             @Main Executor uiExecutor,
-            BatteryController batteryController) {
+            BatteryController batteryController,
+            ConfigurationController configurationController) {
         super(keyguardClockSwitch);
         mResources = resources;
         mStatusBarStateController = statusBarStateController;
@@ -134,6 +161,7 @@
         mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
         mUiExecutor = uiExecutor;
         mBatteryController = batteryController;
+        mConfigurationController = configurationController;
     }
 
     /**
@@ -172,6 +200,12 @@
                 mBatteryController);
         mLargeClockViewController.init();
 
+        mDozeAmount = mStatusBarStateController.getDozeAmount();
+        updateWallpaperColor();
+
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mConfigurationController.addCallback(mConfigurationListener);
+
         // If a smartspace plugin is detected, replace the existing smartspace
         // (keyguard_status_area), and initialize a new session
         mPluginListener = new PluginListener<BcSmartspaceDataPlugin>() {
@@ -186,6 +220,7 @@
 
                 mSmartspaceView = plugin.getView(mView);
                 mSmartspaceView.registerDataProvider(plugin);
+                updateSmartspaceColor();
                 View asView = (View) mSmartspaceView;
 
                 // Place plugin view below normal clock...
@@ -242,6 +277,19 @@
         mPluginManager.addPluginListener(mPluginListener, BcSmartspaceDataPlugin.class, false);
     }
 
+    private void updateWallpaperColor() {
+        mWallpaperTextColor = Utils.getColorAttrDefaultColor(getContext(),
+                R.attr.wallpaperTextColor);
+        updateSmartspaceColor();
+    }
+
+    private void updateSmartspaceColor() {
+        if (mSmartspaceView != null) {
+            int color = ColorUtils.blendARGB(mWallpaperTextColor, mDozeColor, mDozeAmount);
+            mSmartspaceView.setPrimaryTextColor(color);
+        }
+    }
+
     @Override
     protected void onViewDetached() {
         if (CUSTOM_CLOCKS_ENABLED) {
@@ -256,6 +304,8 @@
             mSmartspaceSession = null;
         }
         mPluginManager.removePluginListener(mPluginListener);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+        mConfigurationController.removeCallback(mConfigurationListener);
     }
 
     /**
@@ -399,4 +449,9 @@
     private int getCurrentLayoutDirection() {
         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
     }
+
+    @VisibleForTesting
+    ConfigurationController.ConfigurationListener getConfigurationListener() {
+        return mConfigurationListener;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 8cd68ef..ab15630 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -18,7 +18,7 @@
     val isFaceDisabled: Boolean,
     val isBecauseCannotSkipBouncer: Boolean,
     val isKeyguardGoingAway: Boolean,
-    val isFaceSettingEnabledForUser: Boolean,
+    val isBiometricSettingEnabledForUser: Boolean,
     val isLockIconPressed: Boolean,
     val isScanningAllowedByStrongAuth: Boolean,
     val isPrimaryUser: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 67ee1f4..138dd15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -353,18 +353,15 @@
         }
     };
 
-    private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray();
+    private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
     private BiometricManager mBiometricManager;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
             new IBiometricEnabledOnKeyguardCallback.Stub() {
                 @Override
-                public void onChanged(BiometricSourceType type, boolean enabled, int userId)
-                        throws RemoteException {
+                public void onChanged(boolean enabled, int userId) throws RemoteException {
                     mHandler.post(() -> {
-                        if (type == BiometricSourceType.FACE) {
-                            mFaceSettingEnabledForUser.put(userId, enabled);
-                            updateFaceListeningState();
-                        }
+                        mBiometricEnabledForUser.put(userId, enabled);
+                        updateBiometricListeningState();
                     });
                 }
             };
@@ -1119,6 +1116,7 @@
                 containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
                         || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
         final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+
         return isEncrypted || isLockDown;
     }
 
@@ -1359,7 +1357,7 @@
                 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                         "com.android.systemui:AOD_INTERRUPT_END");
             }
-            mAuthController.onCancelAodInterrupt();
+            mAuthController.onCancelUdfps();
             mIsUdfpsRunningWhileDozing = false;
         }
     };
@@ -1631,6 +1629,12 @@
         mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
     }
 
+    @VisibleForTesting
+    void resetBiometricListeningState() {
+        mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+        mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+    }
+
     private void registerRingerTracker() {
         mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
     }
@@ -1951,7 +1955,7 @@
         mIsFaceEnrolled = whitelistIpcs(
                 () -> mFaceManager != null && mFaceManager.isHardwareDetected()
                         && mFaceManager.hasEnrolledTemplates(userId)
-                        && mFaceSettingEnabledForUser.get(userId));
+                        && mBiometricEnabledForUser.get(userId));
     }
 
     /**
@@ -2099,7 +2103,7 @@
                 shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming))
                 && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser())
                 && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser
-                && allowedOnBouncer;
+                && allowedOnBouncer && mBiometricEnabledForUser.get(getCurrentUser());
         return shouldListen;
     }
 
@@ -2158,7 +2162,7 @@
                 (mBouncer || mAuthInterruptActive || awakeKeyguard
                         || shouldListenForFaceAssistant())
                 && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer
-                && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed
+                && !mKeyguardGoingAway && mBiometricEnabledForUser.get(user) && !mLockIconPressed
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && !mSecureCameraLaunched;
 
@@ -2176,7 +2180,7 @@
                     isFaceDisabled(user),
                     becauseCannotSkipBouncer,
                     mKeyguardGoingAway,
-                    mFaceSettingEnabledForUser.get(user),
+                    mBiometricEnabledForUser.get(user),
                     mLockIconPressed,
                     strongAuthAllowsScanning,
                     mIsPrimaryUser,
@@ -3235,6 +3239,7 @@
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
             pw.println("    udfpsEnrolled=" + isUdfpsEnrolled());
+            pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             if (isUdfpsEnrolled()) {
                 pw.println("        shouldListenForUdfps=" + shouldListenForUdfps());
                 pw.println("        bouncerVisible=" + mBouncer);
@@ -3257,7 +3262,7 @@
             pw.println("    possible=" + isUnlockWithFacePossible(userId));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
-            pw.println("    enabledByUser=" + mFaceSettingEnabledForUser.get(userId));
+            pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
         }
         if (mFaceListenModels != null && !mFaceListenModels.isEmpty()) {
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/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ed8f32f..0c7b55d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -309,18 +309,14 @@
     }
 
     /**
-     * Cancel a fingerprint scan.
-     *
-     * The sensor that triggers an AOD interrupt for fingerprint doesn't give
-     * ACTION_UP/ACTION_CANCEL events, so the scan needs to be cancelled manually. This should be
-     * called when authentication either succeeds or fails. Failing to cancel the scan will leave
-     * the screen in high brightness mode.
+     * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
+     * sensor area even if the user hasn't explicitly lifted their finger yet.
      */
-    public void onCancelAodInterrupt() {
+    public void onCancelUdfps() {
         if (mUdfpsController == null) {
             return;
         }
-        mUdfpsController.onCancelAodInterrupt();
+        mUdfpsController.onCancelUdfps();
     }
 
     private void sendResultAndCleanUp(@DismissedReason int reason,
@@ -499,6 +495,8 @@
             if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
             mCurrentDialog.onError(errorMessage);
         }
+
+        onCancelUdfps();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aa818bf..9239a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -35,15 +36,23 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.media.AudioAttributes;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -94,7 +103,9 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
-    @NonNull private FalsingManager mFalsingManager;
+    @NonNull private final Vibrator mVibrator;
+    @NonNull private final Handler mMainHandler;
+    @NonNull private final FalsingManager mFalsingManager;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -118,6 +129,27 @@
     private boolean mIsAodInterruptActive;
     @Nullable private Runnable mCancelAodTimeoutAction;
 
+    private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
+            new AudioAttributes.Builder()
+                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                    .build();
+
+    private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+    private final VibrationEffect mEffectTextureTick =
+            VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+    private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+    private final VibrationEffect mEffectHeavy =
+            VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+    private final Runnable mAcquiredVibration = new Runnable() {
+        @Override
+        public void run() {
+            String effect = Settings.Global.getString(mContext.getContentResolver(),
+                    "udfps_acquired_type");
+            mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES);
+        }
+    };
+
     /**
      * Keeps track of state within a single FingerprintService request. Note that this state
      * persists across configuration changes, etc, since it is considered a single request.
@@ -227,7 +259,9 @@
     };
 
     @SuppressLint("ClickableViewAccessibility")
-    private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> {
+    private final UdfpsView.OnTouchListener mOnTouchListener = this::onTouch;
+
+    private boolean onTouch(View view, MotionEvent event) {
         UdfpsView udfpsView = (UdfpsView) view;
         final boolean isFingerDown = udfpsView.isIlluminationRequested();
         boolean handled = false;
@@ -251,6 +285,27 @@
                     // data for many other pointers because of multi-touch support.
                     mActivePointerId = event.getPointerId(0);
                     mVelocityTracker.addMovement(event);
+
+                    // TODO: (b/185124905) these settings are for ux testing purposes and should
+                    // be removed (or cached) before going into production
+                    final ContentResolver contentResolver = mContext.getContentResolver();
+                    int startEnabled = Settings.Global.getInt(contentResolver,
+                            "udfps_start", 0);
+                    if (startEnabled > 0) {
+                        String startEffectSetting = Settings.Global.getString(contentResolver,
+                                "udfps_start_type");
+                        mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
+                                VIBRATION_SONIFICATION_ATTRIBUTES);
+                    }
+
+                    int acquiredEnabled = Settings.Global.getInt(contentResolver,
+                            "udfps_acquired", 0);
+                    if (acquiredEnabled > 0) {
+                        int delay = Settings.Global.getInt(contentResolver,
+                                "udfps_acquired_delay", 500);
+                        mMainHandler.removeCallbacks(mAcquiredVibration);
+                        mMainHandler.postDelayed(mAcquiredVibration, delay);
+                    }
                     handled = true;
                 }
                 break;
@@ -307,7 +362,7 @@
                 // Do nothing.
         }
         return handled;
-    };
+    }
 
     @Inject
     public UdfpsController(@NonNull Context context,
@@ -324,6 +379,9 @@
             @NonNull KeyguardViewMediator keyguardViewMediator,
             @NonNull FalsingManager falsingManager) {
         mContext = context;
+        // TODO (b/185124905): inject main handler and vibrator once done prototyping
+        mMainHandler = new Handler(Looper.getMainLooper());
+        mVibrator = context.getSystemService(Vibrator.class);
         mInflater = inflater;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
@@ -559,19 +617,25 @@
         // Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL,
         // we need to be careful about not letting the screen accidentally remain in high brightness
         // mode. As a mitigation, queue a call to cancel the fingerprint scan.
-        mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
+        mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps,
                 AOD_INTERRUPT_TIMEOUT_MILLIS);
         // using a hard-coded value for major and minor until it is available from the sensor
         onFingerDown(screenX, screenY, minor, major);
     }
 
     /**
-     * Cancel fingerprint scan.
+     * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
+     * user explicitly lifts their finger. Generally, this should be called whenever udfps fails
+     * or errors.
      *
-     * This is intended to be called after the fingerprint scan triggered by the AOD interrupt
-     * either succeeds or fails.
+     * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
+     * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
+     * This should be called when authentication either succeeds or fails. Failing to cancel the
+     * scan will leave the screen in high brightness mode and will show the HbmSurfaceView until
+     * the user lifts their finger.
      */
-    void onCancelAodInterrupt() {
+    void onCancelUdfps() {
+        onFingerUp();
         if (!mIsAodInterruptActive) {
             return;
         }
@@ -580,7 +644,6 @@
             mCancelAodTimeoutAction = null;
         }
         mIsAodInterruptActive = false;
-        onFingerUp();
     }
 
     // This method can be called from the UI thread.
@@ -598,6 +661,7 @@
 
     // This method can be called from the UI thread.
     private void onFingerUp() {
+        mMainHandler.removeCallbacks(mAcquiredVibration);
         if (mView == null) {
             Log.w(TAG, "Null view in onFingerUp");
             return;
@@ -617,4 +681,23 @@
         // Do nothing. This method can be implemented for devices that require the high-brightness
         // mode for fingerprint illumination.
     }
+
+    private VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) {
+        if (TextUtils.isEmpty(effect)) {
+            return defaultEffect;
+        }
+
+        switch (effect.toLowerCase()) {
+            case "click":
+                return mEffectClick;
+            case "heavy":
+                return mEffectHeavy;
+            case "texture_tick":
+                return mEffectTextureTick;
+            case "tick":
+                return mEffectTick;
+            default:
+                return defaultEffect;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index ce5795c..767d7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -35,12 +35,10 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -50,18 +48,17 @@
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private final Context mContext;
-    private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
+    private final Lazy<GlobalActionsDialogLite> mGlobalActionsDialogLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
-    private final ExtensionController.Extension<GlobalActionsPanelPlugin> mWalletPluginProvider;
     private final BlurUtils mBlurUtils;
     private final CommandQueue mCommandQueue;
-    private GlobalActionsDialog mGlobalActionsDialog;
+    private GlobalActionsDialogLite mGlobalActionsDialog;
     private boolean mDisabled;
 
     @Inject
     public GlobalActionsImpl(Context context, CommandQueue commandQueue,
-            Lazy<GlobalActionsDialog> globalActionsDialogLazy, BlurUtils blurUtils) {
+            Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils) {
         mContext = context;
         mGlobalActionsDialogLazy = globalActionsDialogLazy;
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
@@ -69,10 +66,6 @@
         mCommandQueue = commandQueue;
         mBlurUtils = blurUtils;
         mCommandQueue.addCallback(this);
-        mWalletPluginProvider = Dependency.get(ExtensionController.class)
-                .newExtension(GlobalActionsPanelPlugin.class)
-                .withPlugin(GlobalActionsPanelPlugin.class)
-                .build();
     }
 
     @Override
@@ -89,8 +82,7 @@
         if (mDisabled) return;
         mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
         mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
-                mDeviceProvisionedController.isDeviceProvisioned(),
-                mWalletPluginProvider.get());
+                mDeviceProvisionedController.isDeviceProvisioned());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 6d1109e..3544f60 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -17,14 +17,18 @@
 package com.android.systemui.navigationbar;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
@@ -80,6 +84,8 @@
         ConfigurationController.ConfigurationListener,
         NavigationModeController.ModeChangedListener, Dumpable {
 
+    private static final float TABLET_MIN_DPS = 600;
+
     private static final String TAG = NavigationBarController.class.getSimpleName();
 
     private final Context mContext;
@@ -107,6 +113,8 @@
     private final Handler mHandler;
     private final DisplayManager mDisplayManager;
     private final NavigationBarOverlayController mNavBarOverlayController;
+    private int mNavMode;
+    private boolean mIsTablet;
 
     /** A displayId - nav bar maps. */
     @VisibleForTesting
@@ -172,11 +180,30 @@
         configurationController.addCallback(this);
         mConfigChanges.applyNewConfig(mContext.getResources());
         mNavBarOverlayController = navBarOverlayController;
+        mNavMode = mNavigationModeController.addListener(this);
         mNavigationModeController.addListener(this);
     }
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
+        boolean isOldConfigTablet = mIsTablet;
+        mIsTablet = isTablet(newConfig);
+        boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+        // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
+        if (isThreeButtonTaskbarFlagEnabled() &&
+                largeScreenChanged && mNavMode == NAV_BAR_MODE_3BUTTON) {
+            if (!mIsTablet) {
+                // Folded state, show 3 button nav bar
+                createNavigationBar(mContext.getDisplay(), null, null);
+            } else {
+                // Unfolded state, hide 3 button nav bars
+                for (int i = 0; i < mNavigationBars.size(); i++) {
+                    removeNavigationBar(mNavigationBars.keyAt(i));
+                }
+            }
+            return;
+        }
+
         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
             for (int i = 0; i < mNavigationBars.size(); i++) {
                 recreateNavigationBar(mNavigationBars.keyAt(i));
@@ -190,7 +217,19 @@
 
     @Override
     public void onNavigationModeChanged(int mode) {
+        final int oldMode = mNavMode;
+        mNavMode = mode;
         mHandler.post(() -> {
+            // create/destroy nav bar based on nav mode only in unfolded state
+            if (isThreeButtonTaskbarFlagEnabled() && oldMode != mNavMode && mIsTablet) {
+                if (oldMode == NAV_BAR_MODE_3BUTTON &&
+                        mNavigationBars.get(mContext.getDisplayId()) == null) {
+                    // We remove navbar for 3 button unfolded, add it back in
+                    createNavigationBar(mContext.getDisplay(), null, null);
+                } else if (mNavMode == NAV_BAR_MODE_3BUTTON) {
+                    removeNavigationBar(mContext.getDisplayId());
+                }
+            }
             for (int i = 0; i < mNavigationBars.size(); i++) {
                 NavigationBar navBar = mNavigationBars.valueAt(i);
                 if (navBar == null) {
@@ -212,6 +251,7 @@
     @Override
     public void onDisplayReady(int displayId) {
         Display display = mDisplayManager.getDisplay(displayId);
+        mIsTablet = isTablet(mContext.getResources().getConfiguration());
         createNavigationBar(display, null /* savedState */, null /* result */);
     }
 
@@ -267,6 +307,10 @@
             return;
         }
 
+        if (isThreeButtonTaskbarEnabled()) {
+            return;
+        }
+
         final int displayId = display.getDisplayId();
         final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
         final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
@@ -398,6 +442,24 @@
         return mNavigationBars.get(DEFAULT_DISPLAY);
     }
 
+    private boolean isThreeButtonTaskbarEnabled() {
+        return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON &&
+                isThreeButtonTaskbarFlagEnabled();
+    }
+
+    private boolean isThreeButtonTaskbarFlagEnabled() {
+        return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
+    }
+
+    private boolean isTablet(Configuration newConfig) {
+        float density = Resources.getSystem().getDisplayMetrics().density;
+        int size = Math.min((int) (density * newConfig.screenWidthDp),
+                (int) (density* newConfig.screenHeightDp));
+        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+        float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+        return (size / densityRatio) >= TABLET_MIN_DPS;
+    }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         for (int i = 0; i < mNavigationBars.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 2ca6a92..01c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -281,7 +281,7 @@
         // When in gestural and the IME is showing, don't use the nearest region since it will take
         // gesture space away from the IME
         info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */,
+        info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
                 false /* inScreen */, false /* useNearestRegion */));
     };
 
@@ -982,7 +982,7 @@
      */
     public void notifyActiveTouchRegions() {
         mOverviewProxyService.onActiveNavBarRegionChanges(
-                getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */,
+                getButtonLocations(true /* includeFloatingButtons */, true /* inScreen */,
                         true /* useNearestRegion */));
     }
 
@@ -995,14 +995,14 @@
     }
 
     /**
-     * @param includeFloatingRotationButton Whether to include the floating rotation button in the
-     *                                      region for all the buttons
+     * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+     *                               the region for all the buttons
      * @param inScreenSpace Whether to return values in screen space or window space
      * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
      * @return
      */
-    private Region getButtonLocations(boolean includeFloatingRotationButton,
-            boolean inScreenSpace, boolean useNearestRegion) {
+    private Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+            boolean useNearestRegion) {
         if (useNearestRegion && !inScreenSpace) {
             // We currently don't support getting the nearest region in anything but screen space
             useNearestRegion = false;
@@ -1014,13 +1014,13 @@
         updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
         updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
         updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
-        if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) {
+        if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
             // Note: this button is floating so the nearest region doesn't apply
             updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
         } else {
             updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
         }
-        if (mNavBarOverlayController.isNavigationBarOverlayEnabled()
+        if (includeFloatingButtons && mNavBarOverlayController.isNavigationBarOverlayEnabled()
                 && mNavBarOverlayController.isVisible()) {
             // Note: this button is floating so the nearest region doesn't apply
             updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace);
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/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/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/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 707135c3..30d9841 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.annotation.IntDef
 import android.content.Context
 import android.content.pm.PackageManager
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
 import com.android.systemui.Dumpable
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -37,9 +39,18 @@
 
     private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
+    @BypassOverride private val bypassOverride: Int
     private var hasFaceFeature: Boolean
     private var pendingUnlock: PendingUnlock? = null
 
+    @IntDef(
+        FACE_UNLOCK_BYPASS_NO_OVERRIDE,
+        FACE_UNLOCK_BYPASS_ALWAYS,
+        FACE_UNLOCK_BYPASS_NEVER
+    )
+    @Retention(AnnotationRetention.SOURCE)
+    private annotation class BypassOverride
+
     /**
      * Pending unlock info:
      *
@@ -60,7 +71,14 @@
      * If face unlock dismisses the lock screen or keeps user on keyguard for the current user.
      */
     var bypassEnabled: Boolean = false
-        get() = field && mKeyguardStateController.isFaceAuthEnabled
+        get() {
+            val enabled = when (bypassOverride) {
+                FACE_UNLOCK_BYPASS_ALWAYS -> true
+                FACE_UNLOCK_BYPASS_NEVER -> false
+                else -> field
+            }
+            return enabled && mKeyguardStateController.isFaceAuthEnabled
+        }
         private set
 
     var bouncerShowing: Boolean = false
@@ -86,6 +104,8 @@
         this.mKeyguardStateController = keyguardStateController
         this.statusBarStateController = statusBarStateController
 
+        bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+
         hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)
         if (!hasFaceFeature) {
             return
@@ -198,5 +218,9 @@
 
     companion object {
         const val BYPASS_PANEL_FADE_DURATION = 67
+
+        private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
+        private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
+        private const val FACE_UNLOCK_BYPASS_NEVER = 2
     }
 }
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/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 55b80dd..db77366 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -633,7 +633,7 @@
                     } else {
                         mNotificationGroupManager.onEntryRemoved(entry);
                     }
-                });
+                }, mSysuiMainExecutor);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index e2e28b8..a870915 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -49,6 +50,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -102,8 +104,11 @@
     private FrameLayout mLargeClockFrame;
     @Mock
     BatteryController mBatteryController;
+    @Mock
+    ConfigurationController mConfigurationController;
 
     private KeyguardClockSwitchController mController;
+    private View mStatusArea;
 
     @Before
     public void setup() {
@@ -111,6 +116,8 @@
 
         when(mView.findViewById(R.id.left_aligned_notification_icon_container))
                 .thenReturn(mNotificationIcons);
+        when(mNotificationIcons.getLayoutParams()).thenReturn(
+                mock(RelativeLayout.LayoutParams.class));
         when(mView.getContext()).thenReturn(getContext());
 
         when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
@@ -135,10 +142,15 @@
                 mPluginManager,
                 mFeatureFlags,
                 mExecutor,
-                mBatteryController);
+                mBatteryController,
+                mConfigurationController);
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+
+        mStatusArea = mock(View.class);
+        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+
     }
 
     @Test
@@ -201,39 +213,40 @@
     public void testSmartspacePluginConnectedRemovesKeyguardStatusArea() {
         mController.init();
 
-        View statusArea = mock(View.class);
-        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
-
-        View nic = mock(View.class);
-        when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
-        when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
-
         BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
         TestView view = mock(TestView.class);
         when(plugin.getView(any())).thenReturn(view);
 
         mController.mPluginListener.onPluginConnected(plugin, mContext);
-        verify(statusArea).setVisibility(View.GONE);
+        verify(mStatusArea).setVisibility(View.GONE);
     }
 
     @Test
     public void testSmartspacePluginDisconnectedShowsKeyguardStatusArea() {
         mController.init();
 
-        View statusArea = mock(View.class);
-        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
-
-        View nic = mock(View.class);
-        when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
-        when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
-
         BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
         TestView view = mock(TestView.class);
         when(plugin.getView(any())).thenReturn(view);
 
         mController.mPluginListener.onPluginConnected(plugin, mContext);
         mController.mPluginListener.onPluginDisconnected(plugin);
-        verify(statusArea).setVisibility(View.VISIBLE);
+        verify(mStatusArea).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testThemeChangeNotifiesSmartspace() {
+        mController.init();
+
+        BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
+        TestView view = mock(TestView.class);
+        when(plugin.getView(any())).thenReturn(view);
+
+        mController.mPluginListener.onPluginConnected(plugin, mContext);
+
+        reset(view);
+        mController.getConfigurationListener().onThemeChanged();
+        verify(view).setPrimaryTextColor(anyInt());
     }
 
     private void verifyAttachment(VerificationMode times) {
@@ -250,5 +263,7 @@
         }
 
         public void registerDataProvider(BcSmartspaceDataPlugin plugin) { }
+
+        public void setPrimaryTextColor(int color) { }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d544f73..42cc1fa9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -49,7 +49,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.face.FaceManager;
@@ -188,8 +187,7 @@
         when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
         doAnswer(invocation -> {
             IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
-            callback.onChanged(BiometricSourceType.FACE, true /* enabled */,
-                    KeyguardUpdateMonitor.getCurrentUser());
+            callback.onChanged(true /* enabled */, KeyguardUpdateMonitor.getCurrentUser());
             return null;
         }).when(mBiometricManager).registerEnabledOnKeyguardCallback(any());
         when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -495,6 +493,11 @@
     }
 
     private void testFingerprintWhenStrongAuth(int strongAuth) {
+        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+        // will trigger updateBiometricListeningState();
+        clearInvocations(mFingerprintManager);
+        mKeyguardUpdateMonitor.resetBiometricListeningState();
+
         when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
         mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
         mTestableLooper.processAllMessages();
@@ -537,7 +540,7 @@
         authCallback.onAuthenticationFailed();
 
         // THEN aod interrupt is cancelled
-        verify(mAuthController).onCancelAodInterrupt();
+        verify(mAuthController).onCancelUdfps();
     }
 
     @Test
@@ -557,7 +560,7 @@
         authCallback.onAuthenticationError(0, "");
 
         // THEN aod interrupt is cancelled
-        verify(mAuthController).onCancelAodInterrupt();
+        verify(mAuthController).onCancelUdfps();
     }
 
     @Test
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/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index bbd3ce8..0aa182f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -259,7 +259,7 @@
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         // WHEN it is cancelled
-        mUdfpsController.onCancelAodInterrupt();
+        mUdfpsController.onCancelUdfps();
         // THEN the illumination is hidden
         verify(mUdfpsView).stopIllumination();
     }
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/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index c49b8e8..78610a2 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -138,6 +138,8 @@
         private static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_Latency_data";
         private static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
                 "latency_observer_sampling_interval";
+        private static final String SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY =
+                "latency_observer_push_interval_minutes";
         private static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY =
                 "latency_histogram_bucket_count";
         private static final String SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY =
@@ -218,7 +220,9 @@
                     mParser.getFloat(
                         SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY,
                         BinderLatencyObserver.BUCKET_SCALE_FACTOR_DEFAULT));
-
+            binderLatencyObserver.setPushInterval(mParser.getInt(
+                    SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY,
+                    BinderLatencyObserver.STATSD_PUSH_INTERVAL_MINUTES_DEFAULT));
 
             final boolean enabled =
                     mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d9cc4b4..4d96162 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1353,8 +1353,8 @@
                 new NetworkInfo(TYPE_NONE, 0, "", ""),
                 new LinkProperties(), new NetworkCapabilities(),
                 new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
-                new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, mQosCallbackTracker,
-                mDeps);
+                new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
+                mLingerDelayMs, mQosCallbackTracker, mDeps);
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -3167,6 +3167,11 @@
                     } else {
                         logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
                     }
+                    break;
+                }
+                case NetworkAgent.EVENT_LINGER_DURATION_CHANGED: {
+                    nai.setLingerDuration((int) arg.second);
+                    break;
                 }
             }
         }
@@ -6516,7 +6521,8 @@
         final NetworkAgentInfo nai = new NetworkAgentInfo(na,
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
                 currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
-                this, mNetd, mDnsResolver, providerId, uid, mQosCallbackTracker, mDeps);
+                this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
+                mQosCallbackTracker, mDeps);
 
         // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
         processCapabilitiesFromAgent(nai, nc);
@@ -7759,7 +7765,7 @@
                     log("   accepting network in place of " + previousSatisfier.toShortString());
                 }
                 previousSatisfier.removeRequest(previousRequest.requestId);
-                previousSatisfier.lingerRequest(previousRequest.requestId, now, mLingerDelayMs);
+                previousSatisfier.lingerRequest(previousRequest.requestId, now);
             } else {
                 if (VDBG || DDBG) log("   accepting network in place of null");
             }
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 611fe7a..2d486c4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -376,7 +376,7 @@
 
     private final LocalLog mListenLog = new LocalLog(200);
 
-    private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+    private List<List<PhysicalChannelConfig>> mPhysicalChannelConfigs;
 
     private boolean[] mIsDataEnabled;
 
@@ -716,7 +716,7 @@
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
-            mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+            mPhysicalChannelConfigs.add(i, new ArrayList<>());
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, new ArrayList<>());
@@ -816,7 +816,7 @@
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
-            mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+            mPhysicalChannelConfigs.add(i, new ArrayList<>());
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, new ArrayList<>());
@@ -1314,8 +1314,9 @@
                     try {
                         r.callback.onPhysicalChannelConfigChanged(
                                 shouldSanitizeLocationForPhysicalChannelConfig(r)
-                                        ? getLocationSanitizedConfigs(mPhysicalChannelConfigs)
-                                        : mPhysicalChannelConfigs);
+                                        ? getLocationSanitizedConfigs(
+                                                mPhysicalChannelConfigs.get(phoneId))
+                                        : mPhysicalChannelConfigs.get(phoneId));
                     } catch (RemoteException ex) {
                         remove(r.binder);
                     }
@@ -2550,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;
         }
@@ -2566,9 +2568,8 @@
         }
 
         synchronized (mRecords) {
-            int phoneId = SubscriptionManager.getPhoneId(subId);
             if (validatePhoneId(phoneId)) {
-                mPhysicalChannelConfigs.set(phoneId, configs.get(phoneId));
+                mPhysicalChannelConfigs.set(phoneId, configs);
                 for (Record r : mRecords) {
                     if (r.matchTelephonyCallbackEvent(
                             TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)
@@ -2775,6 +2776,7 @@
                 pw.println("mDataEnabledReason=" + mDataEnabledReason);
                 pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]);
                 pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
+                pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
                 pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
                 pw.decreaseIndent();
             }
@@ -2785,7 +2787,6 @@
             pw.println("mEmergencyNumberList=" + mEmergencyNumberList);
             pw.println("mDefaultPhoneId=" + mDefaultPhoneId);
             pw.println("mDefaultSubId=" + mDefaultSubId);
-            pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs);
 
             pw.decreaseIndent();
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a1a897..e3b06d6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2500,7 +2500,7 @@
     @Override
     public void batterySendBroadcast(Intent intent) {
         synchronized (this) {
-            broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
+            broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null,
                     OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
                     Binder.getCallingPid(), UserHandle.USER_ALL);
         }
@@ -3940,7 +3940,7 @@
         intent.putExtra(Intent.EXTRA_UID, uid);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
         broadcastIntentLocked(null, null, null, intent,
-                null, null, 0, null, null, null, OP_NONE,
+                null, null, 0, null, null, null, null, OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
                 Binder.getCallingPid(), UserHandle.getUserId(uid));
     }
@@ -7531,7 +7531,7 @@
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                     broadcastIntentLocked(null, null, null, intent,
-                            null, null, 0, null, null, null, OP_NONE,
+                            null, null, 0, null, null, null, null, OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                             currentUserId);
                     intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -7543,8 +7543,8 @@
                                 public void performReceive(Intent intent, int resultCode,
                                         String data, Bundle extras, boolean ordered, boolean sticky,
                                         int sendingUser) {}
-                            }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, OP_NONE, null,
-                            true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+                            }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, OP_NONE,
+                            null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                             UserHandle.USER_ALL);
                 } catch (Throwable e) {
                     Slog.wtf(TAG, "Failed sending first user broadcasts", e);
@@ -12302,7 +12302,7 @@
                     Intent intent = allSticky.get(i);
                     BroadcastQueue queue = broadcastQueueForIntent(intent);
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
-                            null, null, -1, -1, false, null, null, OP_NONE, null, receivers,
+                            null, null, -1, -1, false, null, null, null, OP_NONE, null, receivers,
                             null, 0, null, null, false, true, true, -1, false, null,
                             false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
                     queue.enqueueParallelBroadcastLocked(r);
@@ -12544,13 +12544,13 @@
     final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
             IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
-            boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
-            int realCallingPid, int userId) {
+            Bundle resultExtras, String[] requiredPermissions, String[] excludedPermissions,
+            int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid,
+            int callingUid, int realCallingUid, int realCallingPid, int userId) {
         return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
                 resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
-                appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
-                realCallingPid, userId, false /* allowBackgroundActivityStarts */,
+                excludedPermissions, appOp, bOptions, ordered, sticky, callingPid, callingUid,
+                realCallingUid, realCallingPid, userId, false /* allowBackgroundActivityStarts */,
                 null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
     }
 
@@ -12558,9 +12558,11 @@
     final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
             @Nullable String callerFeatureId, Intent intent, String resolvedType,
             IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
-            boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
-            int realCallingPid, int userId, boolean allowBackgroundActivityStarts,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, int appOp, Bundle bOptions,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int realCallingUid, int realCallingPid, int userId,
+            boolean allowBackgroundActivityStarts,
             @Nullable IBinder backgroundActivityStartsToken,
             @Nullable int[] broadcastAllowList) {
         intent = new Intent(intent);
@@ -13139,8 +13141,8 @@
             final BroadcastQueue queue = broadcastQueueForIntent(intent);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
-                    resultCode, resultData, resultExtras, ordered, sticky, false, userId,
+                    requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers,
+                    resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId,
                     allowBackgroundActivityStarts, backgroundActivityStartsToken,
                     timeoutExempt);
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
@@ -13237,10 +13239,10 @@
             BroadcastQueue queue = broadcastQueueForIntent(intent);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
-                    resultData, resultExtras, ordered, sticky, false, userId,
-                    allowBackgroundActivityStarts, backgroundActivityStartsToken,
-                    timeoutExempt);
+                    requiredPermissions, excludedPermissions, appOp, brOptions,
+                    receivers, resultTo, resultCode, resultData, resultExtras,
+                    ordered, sticky, false, userId, allowBackgroundActivityStarts,
+                    backgroundActivityStartsToken, timeoutExempt);
 
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
 
@@ -13366,14 +13368,14 @@
             String[] requiredPermissions, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
         return broadcastIntentWithFeature(caller, null, intent, resolvedType, resultTo, resultCode,
-                resultData, resultExtras, requiredPermissions, appOp, bOptions, serialized, sticky,
-                userId);
+                resultData, resultExtras, requiredPermissions, null, appOp, bOptions, serialized,
+                sticky, userId);
     }
 
     public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
             Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras,
-            String[] requiredPermissions, int appOp, Bundle bOptions,
+            String[] requiredPermissions, String[] excludedPermissions, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
         enforceNotIsolatedCaller("broadcastIntent");
         synchronized(this) {
@@ -13388,8 +13390,8 @@
                 return broadcastIntentLocked(callerApp,
                         callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                         intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
-                        requiredPermissions, appOp, bOptions, serialized, sticky,
-                        callingPid, callingUid, callingUid, callingPid, userId);
+                        requiredPermissions, excludedPermissions, appOp, bOptions, serialized,
+                        sticky, callingPid, callingUid, callingUid, callingPid, userId);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -13410,7 +13412,7 @@
                     : new String[] {requiredPermission};
             try {
                 return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
-                        resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+                        resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
                         OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
                         realCallingPid, userId, allowBackgroundActivityStarts,
                         backgroundActivityStartsToken,
@@ -15615,7 +15617,7 @@
                     return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
                             null /*callerPackage*/, null /*callingFeatureId*/, intent,
                             null /*resolvedType*/, resultTo, 0 /*resultCode*/, null /*resultData*/,
-                            null /*resultExtras*/, requiredPermissions, AppOpsManager.OP_NONE,
+                            null /*resultExtras*/, requiredPermissions, null, AppOpsManager.OP_NONE,
                             bOptions /*options*/, serialized, false /*sticky*/, callingPid,
                             callingUid, callingUid, callingPid, userId,
                             false /*allowBackgroundStarts*/,
@@ -15740,8 +15742,8 @@
                         | Intent.FLAG_RECEIVER_FOREGROUND
                         | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
                 broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                        OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                        Binder.getCallingPid(), UserHandle.USER_ALL);
+                        null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                        Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
                 if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
                     intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
                     intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
@@ -15751,8 +15753,9 @@
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                     }
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                            OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                            Binder.getCallingPid(), UserHandle.USER_ALL);
+                            null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                            Binder.getCallingUid(), Binder.getCallingPid(),
+                            UserHandle.USER_ALL);
                 }
 
                 // Send a broadcast to PackageInstallers if the configuration change is interesting
@@ -15766,7 +15769,7 @@
                     String[] permissions =
                             new String[] { android.Manifest.permission.INSTALL_PACKAGES };
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
-                            permissions, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                            permissions, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                             Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
                 }
             }
@@ -15791,7 +15794,7 @@
                 }
 
                 broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                        OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
+                        null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
                         Binder.getCallingPid(), UserHandle.USER_ALL);
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 42aac29..6fa8ecd4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -760,7 +760,7 @@
         pw.flush();
         Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle();
         mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null,
-                requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false,
+                requiredPermissions, null, android.app.AppOpsManager.OP_NONE, bundle, true, false,
                 mUserId);
         if (!mAsync) {
             receiver.waitForFinish();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index a5474d0b..f0b116c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1419,6 +1419,7 @@
                 skip = true;
             }
         }
+
         boolean isSingleton = false;
         try {
             isSingleton = mService.isSingleton(info.activityInfo.processName,
@@ -1553,6 +1554,37 @@
                             + info.activityInfo.applicationInfo.uid + " : user is not running");
         }
 
+        if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+            for (int i = 0; i < r.excludedPermissions.length; i++) {
+                String excludedPermission = r.excludedPermissions[i];
+                try {
+                    perm = AppGlobals.getPackageManager()
+                        .checkPermission(excludedPermission,
+                                info.activityInfo.applicationInfo.packageName,
+                                UserHandle
+                                .getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+
+                if (perm == PackageManager.PERMISSION_GRANTED) {
+                    skip = true;
+                    break;
+                }
+
+                int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+                if (appOp != AppOpsManager.OP_NONE) {
+                    if (mService.getAppOpsManager().checkOpNoThrow(appOp,
+                                info.activityInfo.applicationInfo.uid,
+                                info.activityInfo.packageName)
+                            == AppOpsManager.MODE_ALLOWED) {
+                        skip = true;
+                        break;
+                    }
+                }
+            }
+        }
+
         if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
                 r.requiredPermissions != null && r.requiredPermissions.length > 0) {
             for (int i = 0; i < r.requiredPermissions.length; i++) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 198ba34..8015596 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -62,6 +62,7 @@
     final int userId;       // user id this broadcast was for
     final String resolvedType; // the resolved data type
     final String[] requiredPermissions; // permissions the caller has required
+    final String[] excludedPermissions; // permissions to exclude
     final int appOp;        // an app op that is associated with this broadcast
     final BroadcastOptions options; // BroadcastOptions supplied by caller
     final List receivers;   // contains BroadcastFilter and ResolveInfo
@@ -142,6 +143,10 @@
             pw.print(Arrays.toString(requiredPermissions));
             pw.print("  appOp="); pw.println(appOp);
         }
+        if (excludedPermissions != null && excludedPermissions.length > 0) {
+            pw.print(prefix); pw.print("excludedPermissions=");
+            pw.print(Arrays.toString(excludedPermissions));
+        }
         if (options != null) {
             pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
         }
@@ -240,11 +245,11 @@
             Intent _intent, ProcessRecord _callerApp, String _callerPackage,
             @Nullable String _callerFeatureId, int _callingPid, int _callingUid,
             boolean _callerInstantApp, String _resolvedType,
-            String[] _requiredPermissions, int _appOp, BroadcastOptions _options, List _receivers,
-            IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras,
-            boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId,
-            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
-            boolean timeoutExempt) {
+            String[] _requiredPermissions, String[] _excludedPermissions, int _appOp,
+            BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
+            String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
+            boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
+            @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
         if (_intent == null) {
             throw new NullPointerException("Can't construct with a null intent");
         }
@@ -259,6 +264,7 @@
         callerInstantApp = _callerInstantApp;
         resolvedType = _resolvedType;
         requiredPermissions = _requiredPermissions;
+        excludedPermissions = _excludedPermissions;
         appOp = _appOp;
         options = _options;
         receivers = _receivers;
@@ -299,6 +305,7 @@
         userId = from.userId;
         resolvedType = from.resolvedType;
         requiredPermissions = from.requiredPermissions;
+        excludedPermissions = from.excludedPermissions;
         appOp = from.appOp;
         options = from.options;
         receivers = from.receivers;
@@ -356,8 +363,8 @@
         // build a new BroadcastRecord around that single-target list
         BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                 callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode,
-                resultData, resultExtras, ordered, sticky, initialSticky, userId,
+                requiredPermissions, excludedPermissions, appOp, options, splitReceivers, resultTo,
+                resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
                 allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
 
         split.splitToken = this.splitToken;
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 984fe40..7562098 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -124,7 +124,7 @@
                 REASON_PRE_BOOT_COMPLETED, "");
         synchronized (mService) {
             mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null,
-                    null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
+                    null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
                     false, ActivityManagerService.MY_PID,
                     Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a5d0e72..ba3e1fb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2936,8 +2936,8 @@
             // TODO b/64165549 Verify that mLock is not held before calling AMS methods
             synchronized (mService) {
                 return mService.broadcastIntentLocked(null, null, null, intent, resolvedType,
-                        resultTo, resultCode, resultData, resultExtras, requiredPermissions, appOp,
-                        bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
+                        resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
+                        appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
                         realCallingPid, userId);
             }
         }
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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 419e686..3f07572 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1827,7 +1827,8 @@
             }
         }
 
-        mHistoricalRegistry.clearHistory(uid, packageName);
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+                    mHistoricalRegistry, uid, packageName));
     }
 
     public void uidRemoved(int uid) {
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/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index cb7c568..a546a60 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -35,7 +35,6 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -51,6 +50,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
@@ -338,18 +338,31 @@
         private static final boolean DEFAULT_APP_ENABLED = true;
         private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
 
+        // Some devices that shipped before S already have face-specific settings. Instead of
+        // migrating, which is complicated, let's just keep using the existing settings.
+        private final boolean mUseLegacyFaceOnlySettings;
+
+        // Only used for legacy face-only devices
         private final Uri FACE_UNLOCK_KEYGUARD_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
         private final Uri FACE_UNLOCK_APP_ENABLED =
                 Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+
+        // Continues to be used, even though it's face-specific.
         private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
                 Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
 
+        // Used for all devices other than legacy face-only devices
+        private final Uri BIOMETRIC_KEYGUARD_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED);
+        private final Uri BIOMETRIC_APP_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
+
         private final ContentResolver mContentResolver;
         private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
 
-        private final Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>();
-        private final Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>();
+        private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
+        private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
         private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
 
         /**
@@ -362,21 +375,44 @@
             super(handler);
             mContentResolver = context.getContentResolver();
             mCallbacks = callbacks;
+
+            final boolean hasFingerprint = context.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
+            final boolean hasFace = context.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_FACE);
+
+            // Use the legacy setting on face-only devices that shipped on or before Q
+            mUseLegacyFaceOnlySettings =
+                    Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
+                    && hasFace && !hasFingerprint;
+
             updateContentObserver();
         }
 
         public void updateContentObserver() {
             mContentResolver.unregisterContentObserver(this);
-            mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
-                    false /* notifyForDescendents */,
-                    this /* observer */,
-                    UserHandle.USER_ALL);
-            mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
-                    false /* notifyForDescendents */,
-                    this /* observer */,
-                    UserHandle.USER_ALL);
+
+            if (mUseLegacyFaceOnlySettings) {
+                mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+                mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+            } else {
+                mContentResolver.registerContentObserver(BIOMETRIC_KEYGUARD_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+                mContentResolver.registerContentObserver(BIOMETRIC_APP_ENABLED,
+                        false /* notifyForDescendants */,
+                        this /* observer */,
+                        UserHandle.USER_ALL);
+            }
             mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
-                    false /* notifyForDescendents */,
+                    false /* notifyForDescendants */,
                     this /* observer */,
                     UserHandle.USER_ALL);
         }
@@ -384,7 +420,7 @@
         @Override
         public void onChange(boolean selfChange, Uri uri, int userId) {
             if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) {
-                mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+                mBiometricEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
                                 mContentResolver,
                                 Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
                                 DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
@@ -394,7 +430,7 @@
                     notifyEnabledOnKeyguardCallbacks(userId);
                 }
             } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
-                mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+                mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
                                 mContentResolver,
                                 Settings.Secure.FACE_UNLOCK_APP_ENABLED,
                                 DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
@@ -405,22 +441,45 @@
                                 Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
                                 DEFAULT_ALWAYS_REQUIRE_CONFIRMATION ? 1 : 0 /* default */,
                                 userId) != 0);
+            } else if (BIOMETRIC_KEYGUARD_ENABLED.equals(uri)) {
+                mBiometricEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
+                        DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
+
+                if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+                    notifyEnabledOnKeyguardCallbacks(userId);
+                }
+            } else if (BIOMETRIC_APP_ENABLED.equals(uri)) {
+                mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+                        mContentResolver,
+                        Settings.Secure.BIOMETRIC_APP_ENABLED,
+                        DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+                        userId) != 0);
             }
         }
 
-        public boolean getFaceEnabledOnKeyguard() {
-            final int user = ActivityManager.getCurrentUser();
-            if (!mFaceEnabledOnKeyguard.containsKey(user)) {
-                onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user);
+        public boolean getEnabledOnKeyguard(int userId) {
+            if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
+                if (mUseLegacyFaceOnlySettings) {
+                    onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
+                } else {
+                    onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+                }
             }
-            return mFaceEnabledOnKeyguard.get(user);
+            return mBiometricEnabledOnKeyguard.get(userId);
         }
 
-        public boolean getFaceEnabledForApps(int userId) {
-            if (!mFaceEnabledForApps.containsKey(userId)) {
-                onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+        public boolean getEnabledForApps(int userId) {
+            if (!mBiometricEnabledForApps.containsKey(userId)) {
+                if (mUseLegacyFaceOnlySettings) {
+                    onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+                } else {
+                    onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+                }
             }
-            return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
+            return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
         }
 
         public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
@@ -442,8 +501,8 @@
         void notifyEnabledOnKeyguardCallbacks(int userId) {
             List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
             for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).notify(BiometricSourceType.FACE,
-                        mFaceEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
+                callbacks.get(i).notify(
+                        mBiometricEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
                         userId);
             }
         }
@@ -462,9 +521,9 @@
             }
         }
 
-        void notify(BiometricSourceType sourceType, boolean enabled, int userId) {
+        void notify(boolean enabled, int userId) {
             try {
-                mCallback.onChanged(sourceType, enabled, userId);
+                mCallback.onChanged(enabled, userId);
             } catch (DeadObjectException e) {
                 Slog.w(TAG, "Death while invoking notify", e);
                 mEnabledOnKeyguardCallbacks.remove(this);
@@ -747,8 +806,8 @@
 
             mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
             try {
-                callback.onChanged(BiometricSourceType.FACE,
-                        mSettingObserver.getFaceEnabledOnKeyguard(), callingUserId);
+                callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId),
+                        callingUserId);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception", e);
             }
@@ -1347,6 +1406,9 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
+        pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings);
+        pw.println();
+
         pw.println("Sensors:");
         for (BiometricSensor sensor : mSensors) {
             pw.println(" " + sensor);
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 262cb36..c4bd18b 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -197,17 +197,7 @@
 
     private static boolean isEnabledForApp(BiometricService.SettingObserver settingObserver,
             @BiometricAuthenticator.Modality int modality, int userId) {
-        switch (modality) {
-            case TYPE_FINGERPRINT:
-                return true;
-            case TYPE_IRIS:
-                return true;
-            case TYPE_FACE:
-                return settingObserver.getFaceEnabledForApps(userId);
-            default:
-                Slog.w(TAG, "Unsupported modality: " + modality);
-                return false;
-        }
+        return settingObserver.getEnabledForApps(userId);
     }
 
     private static boolean isBiometricDisabledByDevicePolicy(
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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1bbcede..b2d35f4 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,9 +15,6 @@
  */
 package com.android.server.camera;
 
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.os.Build.VERSION_CODES.M;
 
 import android.annotation.IntDef;
@@ -27,15 +24,12 @@
 import android.app.ActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.hardware.CameraSessionStats;
 import android.hardware.CameraStreamStats;
 import android.hardware.ICameraService;
@@ -43,7 +37,6 @@
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
-import android.metrics.LogMaker;
 import android.nfc.INfcAdapter;
 import android.os.Binder;
 import android.os.Handler;
@@ -60,17 +53,16 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.Display;
+import android.view.IDisplayWindowListener;
 import android.view.Surface;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.framework.protobuf.nano.MessageNano;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.annotation.Retention;
@@ -223,6 +215,37 @@
         }
     }
 
+    private final class DisplayWindowListener extends IDisplayWindowListener.Stub {
+
+        @Override
+        public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+            ICameraService cs = getCameraServiceRawLocked();
+            if (cs == null) return;
+
+            try {
+                cs.notifyDisplayConfigurationChange();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e);
+                // Not much we can do if camera service is dead.
+            }
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) { }
+
+        @Override
+        public void onDisplayRemoved(int displayId) { }
+
+        @Override
+        public void onFixedRotationStarted(int displayId, int newRotation) { }
+
+        @Override
+        public void onFixedRotationFinished(int displayId) { }
+    }
+
+
+    private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener();
+
     private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
 
     private final class TaskInfo {
@@ -236,13 +259,12 @@
     private final class TaskStateHandler extends TaskStackListener {
         private final Object mMapLock = new Object();
 
-        // maps the current top level task id to its corresponding package name
+        // maps the package name to its corresponding current top level task id
         @GuardedBy("mMapLock")
         private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>();
 
         @Override
-        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
+        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
             synchronized (mMapLock) {
                 TaskInfo info = new TaskInfo();
                 info.frontTaskId = taskInfo.taskId;
@@ -257,7 +279,7 @@
         }
 
         @Override
-        public void onTaskRemoved(int taskId) throws RemoteException {
+        public void onTaskRemoved(int taskId) {
             synchronized (mMapLock) {
                 for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){
                     if (entry.getValue().frontTaskId == taskId) {
@@ -319,7 +341,7 @@
         /**
          * Gets whether crop-rotate-scale is needed.
          */
-        private boolean getNeedCropRotateScale(Context ctx, String packageName,
+        private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
                 @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) {
             if (taskInfo == null) {
                 return false;
@@ -334,7 +356,7 @@
 
             // Only enable the crop-rotate-scale workaround if the app targets M or below and is not
             // resizeable.
-            if ((ctx != null) && !isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
+            if (!isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
                 Slog.v(TAG,
                         "The activity is N or above and claims to support resizeable-activity. "
                                 + "Crop-rotate-scale is disabled.");
@@ -372,12 +394,8 @@
                             taskInfo.isFixedOrientationLandscape);
             // We need to do crop-rotate-scale when camera is landscape and activity is portrait or
             // vice versa.
-            if ((taskInfo.isFixedOrientationPortrait && landscapeCamera)
-                    || (taskInfo.isFixedOrientationLandscape && !landscapeCamera)) {
-                return true;
-            } else {
-                return false;
-            }
+            return (taskInfo.isFixedOrientationPortrait && landscapeCamera)
+                    || (taskInfo.isFixedOrientationLandscape && !landscapeCamera);
         }
 
         @Override
@@ -389,14 +407,10 @@
                 return false;
             }
 
-            // A few remaining todos:
-            // 1) Do the same check when working in WM compatible mode. The sequence needs
-            //    to be adjusted and use orientation events as triggers for all active camera
-            //    clients.
-            // 2) Modify the sensor orientation in camera characteristics along with any 3A regions
-            //    in capture requests/results to account for thea physical rotation. The former
-            //    is somewhat tricky as it assumes that camera clients always check for the current
-            //    value by retrieving the camera characteristics from the camera device.
+            // TODO: Modify the sensor orientation in camera characteristics along with any 3A
+            //  regions in capture requests/results to account for thea physical rotation. The
+            //  former is somewhat tricky as it assumes that camera clients always check for the
+            //  current value by retrieving the camera characteristics from the camera device.
             return getNeedCropRotateScale(mContext, packageName,
                     mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation,
                     lensFacing);
@@ -522,18 +536,25 @@
 
         publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy);
         publishLocalService(CameraServiceProxy.class, this);
-
-        try {
-            ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to register task stack listener!");
-        }
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_BOOT_COMPLETED) {
             CameraStatsJobService.schedule(mContext);
+
+            try {
+                ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register task stack listener!");
+            }
+
+            try {
+                WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
+                        mDisplayWindowListener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register display window listener!");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4d310cb..584174e 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -59,6 +59,7 @@
 import com.android.server.ConnectivityService;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
@@ -281,6 +282,9 @@
      */
     public static final int ARG_AGENT_SUCCESS = 1;
 
+    // How long this network should linger for.
+    private int mLingerDurationMs;
+
     // All inactivity timers for this network, sorted by expiry time. A timer is added whenever
     // a request is moved to a network with a better score, regardless of whether the network is or
     // was lingering or not. An inactivity timer is also added when a network connects
@@ -349,7 +353,8 @@
             @NonNull NetworkScore score, Context context,
             Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid,
-            QosCallbackTracker qosCallbackTracker, ConnectivityService.Dependencies deps) {
+            int lingerDurationMs, QosCallbackTracker qosCallbackTracker,
+            ConnectivityService.Dependencies deps) {
         Objects.requireNonNull(net);
         Objects.requireNonNull(info);
         Objects.requireNonNull(lp);
@@ -370,6 +375,7 @@
         mHandler = handler;
         this.factorySerialNumber = factorySerialNumber;
         this.creatorUid = creatorUid;
+        mLingerDurationMs = lingerDurationMs;
         mQosCallbackTracker = qosCallbackTracker;
     }
 
@@ -685,6 +691,12 @@
             mHandler.obtainMessage(NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED,
                     teardownDelayMs, 0, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
         }
+
+        @Override
+        public void sendLingerDuration(final int durationMs) {
+            mHandler.obtainMessage(NetworkAgent.EVENT_LINGER_DURATION_CHANGED,
+                    new Pair<>(NetworkAgentInfo.this, durationMs)).sendToTarget();
+        }
     }
 
     /**
@@ -954,13 +966,14 @@
 
     /**
      * Sets the specified requestId to linger on this network for the specified time. Called by
-     * ConnectivityService when the request is moved to another network with a higher score, or
+     * ConnectivityService when any request is moved to another network with a higher score, or
      * when a network is newly created.
      *
      * @param requestId The requestId of the request that no longer need to be served by this
      *                  network. Or {@link NetworkRequest.REQUEST_ID_NONE} if this is the
-     *                  {@code LingerTimer} for a newly created network.
+     *                  {@code InactivityTimer} for a newly created network.
      */
+    // TODO: Consider creating a dedicated function for nascent network, e.g. start/stopNascent.
     public void lingerRequest(int requestId, long now, long duration) {
         if (mInactivityTimerForRequest.get(requestId) != null) {
             // Cannot happen. Once a request is lingering on a particular network, we cannot
@@ -976,6 +989,19 @@
     }
 
     /**
+     * Sets the specified requestId to linger on this network for the timeout set when
+     * initializing or modified by {@link #setLingerDuration(int)}. Called by
+     * ConnectivityService when any request is moved to another network with a higher score.
+     *
+     * @param requestId The requestId of the request that no longer need to be served by this
+     *                  network.
+     * @param now current system timestamp obtained by {@code SystemClock.elapsedRealtime}.
+     */
+    public void lingerRequest(int requestId, long now) {
+        lingerRequest(requestId, now, mLingerDurationMs);
+    }
+
+    /**
      * Cancel lingering. Called by ConnectivityService when a request is added to this network.
      * Returns true if the given requestId was lingering on this network, false otherwise.
      */
@@ -1012,6 +1038,7 @@
         }
 
         if (newExpiry > 0) {
+            // If the newExpiry timestamp is in the past, the wakeup message will fire immediately.
             mInactivityMessage = new WakeupMessage(
                     mContext, mHandler,
                     "NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */,
@@ -1041,8 +1068,33 @@
     }
 
     /**
-     * Return whether the network is just connected and about to be torn down because of not
-     * satisfying any request.
+     * Set the linger duration for this NAI.
+     * @param durationMs The new linger duration, in milliseconds.
+     */
+    public void setLingerDuration(final int durationMs) {
+        final long diff = durationMs - mLingerDurationMs;
+        final ArrayList<InactivityTimer> newTimers = new ArrayList<>();
+        for (final InactivityTimer timer : mInactivityTimers) {
+            if (timer.requestId == NetworkRequest.REQUEST_ID_NONE) {
+                // Don't touch nascent timer, re-add as is.
+                newTimers.add(timer);
+            } else {
+                newTimers.add(new InactivityTimer(timer.requestId, timer.expiryMs + diff));
+            }
+        }
+        mInactivityTimers.clear();
+        mInactivityTimers.addAll(newTimers);
+        updateInactivityTimer();
+        mLingerDurationMs = durationMs;
+    }
+
+    /**
+     * Return whether the network satisfies no request, but is still being kept up
+     * because it has just connected less than
+     * {@code ConnectivityService#DEFAULT_NASCENT_DELAY_MS}ms ago and is thus still considered
+     * nascent. Note that nascent mechanism uses inactivity timer which isn't
+     * associated with a request. Thus, use {@link NetworkRequest#REQUEST_ID_NONE} to identify it.
+     *
      */
     public boolean isNascent() {
         return mInactive && mInactivityTimers.size() == 1
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed8ea18..7994fcc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4100,13 +4100,10 @@
     }
 
     @Override
-    public void removeImeSurfaceFromWindow(IBinder windowToken,
-            IVoidResultCallback resultCallback) {
-        CallbackUtils.onResult(resultCallback, () -> {
-            // No permission check, because we'll only execute the request if the calling window is
-            // also the current IME client.
-            mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
-        });
+    public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+        // No permission check, because we'll only execute the request if the calling window is
+        // also the current IME client.
+        mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index ef1489b..6244743 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1510,10 +1510,8 @@
 
         @BinderThread
         @Override
-        public void removeImeSurfaceFromWindow(IBinder windowToken,
-                IVoidResultCallback resultCallback) {
+        public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
             reportNotSupported();
-            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 91a66ac..dd80e16 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -636,9 +636,25 @@
             Objects.requireNonNull(component);
 
             // All right, create the sender.
-            Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
+            final int callingUid = injectBinderCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
+                final PackageManagerInternal pmInt =
+                        LocalServices.getService(PackageManagerInternal.class);
+                Intent packageIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
+                        .setPackage(component.getPackageName());
+                List<ResolveInfo> apps = pmInt.queryIntentActivities(packageIntent,
+                        packageIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                        callingUid, user.getIdentifier());
+                // ensure that the component is present in the list
+                if (!apps.stream().anyMatch(
+                        ri -> component.getClassName().equals(ri.activityInfo.name))) {
+                    return null;
+                }
+
+                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
                 final PendingIntent pi = PendingIntent.getActivityAsUser(
                         mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
                                 | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2849982..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) {
@@ -15315,9 +15238,8 @@
             final BroadcastOptions bOptions = getTemporaryAppAllowlistBroadcastOptions(
                     REASON_LOCKED_BOOT_COMPLETED);
             am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null,
-                    requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
-                    false, false,
-                    userId);
+                    requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+                    bOptions.toBundle(), false, false, userId);
 
             // Deliver BOOT_COMPLETED only if user is unlocked
             final UserManagerInternal umInternal = mInjector.getUserManagerInternal();
@@ -15327,9 +15249,8 @@
                     bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                 }
                 am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null,
-                        requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
-                        false, false,
-                        userId);
+                        requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+                        bOptions.toBundle(), false, false, userId);
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -22197,7 +22118,7 @@
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             try {
                 am.broadcastIntentWithFeature(null, null, intent, null, null,
-                        0, null, null, null, android.app.AppOpsManager.OP_NONE,
+                        0, null, null, null, null, android.app.AppOpsManager.OP_NONE,
                         null, false, false, userId);
             } catch (RemoteException e) {
             }
@@ -23959,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;
@@ -24107,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();
@@ -24435,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);
+            }
+        }
     }
 
     /**
@@ -27810,8 +27750,8 @@
             };
             try {
                 am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null,
-                        requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false,
-                        UserHandle.USER_ALL);
+                        requiredPermissions, null, android.app.AppOpsManager.OP_NONE, null, false,
+                        false, UserHandle.USER_ALL);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
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/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 2a17c6d..3f00a9d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -29,6 +29,7 @@
 import android.os.ServiceSpecificException;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.UUID;
 
 public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
@@ -110,6 +111,7 @@
     public List<DomainOwner> getOwnersForDomain(@NonNull String domain,
             @UserIdInt int userId) {
         try {
+            Objects.requireNonNull(domain);
             return mService.getOwnersForDomain(domain, userId);
         } catch (Exception e) {
             throw rethrow(e);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 4ae79a2..f0fdad0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -742,6 +742,7 @@
     }
 
     public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
+        Objects.requireNonNull(domain);
         mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
                 userId);
 
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/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index 474ce59..816c81d 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -330,7 +330,9 @@
 
     private boolean isEnabled() {
         return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
-                DEFAULT_FEATURE_ENABLED);
+                DEFAULT_FEATURE_ENABLED)
+                && mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_flipToScreenOffEnabled);
     }
 
     private float getAccelerationThreshold() {
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/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4f5e8fa..072cc16f 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -16,26 +16,22 @@
 
 package com.android.server.timedetector;
 
-import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
-import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
-import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
-
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.os.Build;
+import android.database.ContentObserver;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.server.timedetector.TimeDetectorStrategy.Origin;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
 
 import java.time.Instant;
 import java.util.Objects;
@@ -43,62 +39,71 @@
 /**
  * The real implementation of {@link TimeDetectorStrategyImpl.Environment} used on device.
  */
-public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
+final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
 
-    private static final String TAG = TimeDetectorService.TAG;
-
-    private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
-
-    /**
-     * Time in the past. If automatic time suggestion is before this point, it's
-     * incorrect for sure.
-     */
-    private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
-            Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
-
-    /**
-     * By default telephony and network only suggestions are accepted and telephony takes
-     * precedence over network.
-     */
-    private static final @Origin int[] DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES =
-            { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
-
-    /**
-     * If a newly calculated system clock time and the current system clock time differs by this or
-     * more the system clock will actually be updated. Used to prevent the system clock being set
-     * for only minor differences.
-     */
-    private final int mSystemClockUpdateThresholdMillis;
+    private static final String LOG_TAG = TimeDetectorService.TAG;
 
     @NonNull private final Context mContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final ContentResolver mContentResolver;
     @NonNull private final PowerManager.WakeLock mWakeLock;
     @NonNull private final AlarmManager mAlarmManager;
     @NonNull private final UserManager mUserManager;
-    @NonNull private final int[] mOriginPriorities;
 
-    public EnvironmentImpl(@NonNull Context context) {
+    // @NonNull after setConfigChangeListener() is called.
+    @GuardedBy("this")
+    private ConfigurationChangeListener mConfigChangeListener;
+
+    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
+            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
         mContext = Objects.requireNonNull(context);
         mContentResolver = Objects.requireNonNull(context.getContentResolver());
+        mHandler = Objects.requireNonNull(handler);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
 
         PowerManager powerManager = context.getSystemService(PowerManager.class);
         mWakeLock = Objects.requireNonNull(
-                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG));
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG));
 
         mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class));
 
         mUserManager = Objects.requireNonNull(context.getSystemService(UserManager.class));
 
-        mSystemClockUpdateThresholdMillis =
-                SystemProperties.getInt("ro.sys.time_detector_update_diff",
-                        SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+        // Wire up the config change listeners. All invocations are performed on the mHandler
+        // thread.
 
-        mOriginPriorities = getOriginPriorities(context);
+        ContentResolver contentResolver = context.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        handleAutoTimeDetectionChangedOnHandlerThread();
+                    }
+                });
+    }
+
+    /** Internal method for handling the auto time setting being changed. */
+    private void handleAutoTimeDetectionChangedOnHandlerThread() {
+        synchronized (this) {
+            if (mConfigChangeListener == null) {
+                Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+            }
+            mConfigChangeListener.onChange();
+        }
+    }
+
+    @Override
+    public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+        synchronized (this) {
+            mConfigChangeListener = Objects.requireNonNull(listener);
+        }
     }
 
     @Override
     public int systemClockUpdateThresholdMillis() {
-        return mSystemClockUpdateThresholdMillis;
+        return mServiceConfigAccessor.systemClockUpdateThresholdMillis();
     }
 
     @Override
@@ -112,12 +117,12 @@
 
     @Override
     public Instant autoTimeLowerBound() {
-        return TIME_LOWER_BOUND;
+        return mServiceConfigAccessor.autoTimeLowerBound();
     }
 
     @Override
     public int[] autoOriginPriorities() {
-        return mOriginPriorities;
+        return mServiceConfigAccessor.getOriginPriorities();
     }
 
     @Override
@@ -131,7 +136,7 @@
     @Override
     public void acquireWakeLock() {
         if (mWakeLock.isHeld()) {
-            Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held");
+            Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " already held");
         }
         mWakeLock.acquire();
     }
@@ -160,7 +165,7 @@
 
     private void checkWakeLockHeld() {
         if (!mWakeLock.isHeld()) {
-            Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
+            Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " not held");
         }
     }
 
@@ -168,20 +173,4 @@
         UserHandle userHandle = UserHandle.of(userId);
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
     }
-
-    private static int[] getOriginPriorities(@NonNull Context context) {
-        String[] originStrings =
-                context.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
-        if (originStrings.length == 0) {
-            return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
-        } else {
-            int[] origins = new int[originStrings.length];
-            for (int i = 0; i < originStrings.length; i++) {
-                int origin = stringToOrigin(originStrings[i]);
-                origins[i] = origin;
-            }
-
-            return origins;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
new file mode 100644
index 0000000..be4432a
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -0,0 +1,141 @@
+/*
+ * 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.timedetector;
+
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
+import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.ArraySet;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A singleton that provides access to service configuration for time detection. This hides how
+ * configuration is split between static, compile-time config and dynamic, server-pushed flags. It
+ * provides a rudimentary mechanism to signal when values have changed.
+ */
+final class ServiceConfigAccessor {
+
+    private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
+
+    /**
+     * By default telephony and network only suggestions are accepted and telephony takes
+     * precedence over network.
+     */
+    private static final @TimeDetectorStrategy.Origin int[]
+            DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES = { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
+
+    /**
+     * Time in the past. If an automatic time suggestion is before this point, it is sure to be
+     * incorrect.
+     */
+    private static final Instant TIME_LOWER_BOUND_DEFAULT = Instant.ofEpochMilli(
+            Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
+
+    private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
+            new ArraySet<>(new String[] {
+            }));
+
+    private static final Object SLOCK = new Object();
+
+    /** The singleton instance. Initialized once in {@link #getInstance(Context)}. */
+    @GuardedBy("SLOCK")
+    @Nullable
+    private static ServiceConfigAccessor sInstance;
+
+    @NonNull private final Context mContext;
+    @NonNull private final ServerFlags mServerFlags;
+    @NonNull private final int[] mOriginPriorities;
+
+    /**
+     * If a newly calculated system clock time and the current system clock time differs by this or
+     * more the system clock will actually be updated. Used to prevent the system clock being set
+     * for only minor differences.
+     */
+    private final int mSystemClockUpdateThresholdMillis;
+
+    private ServiceConfigAccessor(@NonNull Context context) {
+        mContext = Objects.requireNonNull(context);
+        mServerFlags = ServerFlags.getInstance(mContext);
+        mOriginPriorities = getOriginPrioritiesInternal();
+        mSystemClockUpdateThresholdMillis =
+                SystemProperties.getInt("ro.sys.time_detector_update_diff",
+                        SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+    }
+
+    /** Returns the singleton instance. */
+    static ServiceConfigAccessor getInstance(Context context) {
+        synchronized (SLOCK) {
+            if (sInstance == null) {
+                sInstance = new ServiceConfigAccessor(context);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Adds a listener that will be called when server flags related to this class change. The
+     * callbacks are delivered on the main looper thread.
+     *
+     * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
+     * method.
+     */
+    void addListener(@NonNull ConfigurationChangeListener listener) {
+        mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH);
+    }
+
+    @NonNull
+    int[] getOriginPriorities() {
+        return mOriginPriorities;
+    }
+
+    int systemClockUpdateThresholdMillis() {
+        return mSystemClockUpdateThresholdMillis;
+    }
+
+    Instant autoTimeLowerBound() {
+        return TIME_LOWER_BOUND_DEFAULT;
+    }
+
+    private int[] getOriginPrioritiesInternal() {
+        String[] originStrings =
+                mContext.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
+        if (originStrings.length == 0) {
+            return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
+        } else {
+            int[] origins = new int[originStrings.length];
+            for (int i = 0; i < originStrings.length; i++) {
+                int origin = stringToOrigin(originStrings[i]);
+                origins[i] = origin;
+            }
+
+            return origins;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 27e2ee5..14cab38 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -27,12 +27,9 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
 import android.os.Binder;
 import android.os.Handler;
-import android.provider.Settings;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -64,7 +61,16 @@
 
         @Override
         public void onStart() {
-            TimeDetectorService service = TimeDetectorService.create(getContext());
+            Context context = getContext();
+            Handler handler = FgThread.getHandler();
+
+            ServiceConfigAccessor serviceConfigAccessor =
+                    ServiceConfigAccessor.getInstance(context);
+            TimeDetectorStrategy timeDetectorStrategy =
+                    TimeDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+
+            TimeDetectorService service =
+                    new TimeDetectorService(context, handler, timeDetectorStrategy);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
@@ -77,28 +83,6 @@
     @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
     @NonNull private final CallerIdentityInjector mCallerIdentityInjector;
 
-    private static TimeDetectorService create(@NonNull Context context) {
-        TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context);
-        TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(environment);
-
-        Handler handler = FgThread.getHandler();
-        TimeDetectorService timeDetectorService =
-                new TimeDetectorService(context, handler, timeDetectorStrategy);
-
-        // Wire up event listening.
-        ContentResolver contentResolver = context.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
-                new ContentObserver(handler) {
-                    @Override
-                    public void onChange(boolean selfChange) {
-                        timeDetectorService.handleAutoTimeDetectionChanged();
-                    }
-                });
-
-        return timeDetectorService;
-    }
-
     @VisibleForTesting
     public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull TimeDetectorStrategy timeDetectorStrategy) {
@@ -136,6 +120,7 @@
 
     @Override
     public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
+        enforceManageTimeDetectorPermission();
         // TODO(b/172891783) Add actual logic
         return false;
     }
@@ -185,12 +170,6 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
     }
 
-    /** Internal method for handling the auto time setting being changed. */
-    @VisibleForTesting
-    public void handleAutoTimeDetectionChanged() {
-        mHandler.post(mTimeDetectorStrategy::handleAutoTimeConfigChanged);
-    }
-
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index cde66be..be382f0 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -92,12 +92,6 @@
     /** Returns the configuration that controls time detector behaviour for specified user. */
     ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
 
-    /**
-     * Handles the auto-time configuration changing For example, when the auto-time setting is
-     * toggled on or off.
-     */
-    void handleAutoTimeConfigChanged();
-
     // Utility methods below are to be moved to a better home when one becomes more obvious.
 
     /**
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 289d8d6..db8a59e 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -29,6 +29,8 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.TelephonyTimeSuggestion;
+import android.content.Context;
+import android.os.Handler;
 import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
@@ -37,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.timezonedetector.ArrayMapWithHistory;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ReferenceWithHistory;
 
 import java.time.Instant;
@@ -132,6 +135,12 @@
     public interface Environment {
 
         /**
+         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
+         * changes that could affect time detection. This is invoked during system server setup.
+         */
+        void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
+
+        /**
          * The absolute threshold below which the system clock need not be updated. i.e. if setting
          * the system clock would adjust it by less than this (either backwards or forwards) then it
          * need not be set.
@@ -178,8 +187,19 @@
         void releaseWakeLock();
     }
 
+    static TimeDetectorStrategy create(
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+
+        TimeDetectorStrategyImpl.Environment environment =
+                new EnvironmentImpl(context, handler, serviceConfigAccessor);
+        return new TimeDetectorStrategyImpl(environment);
+    }
+
+    @VisibleForTesting
     TimeDetectorStrategyImpl(@NonNull Environment environment) {
         mEnvironment = Objects.requireNonNull(environment);
+        mEnvironment.setConfigChangeListener(this::handleAutoTimeConfigChanged);
     }
 
     @Override
@@ -279,8 +299,7 @@
         return mEnvironment.configurationInternal(userId);
     }
 
-    @Override
-    public synchronized void handleAutoTimeConfigChanged() {
+    private synchronized void handleAutoTimeConfigChanged() {
         boolean enabled = mEnvironment.isAutoTimeDetectionEnabled();
         // When automatic time detection is enabled we update the system clock instantly if we can.
         // Conversely, when automatic time detection is disabled we leave the clock as it is.
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 0e5f3bf..b84f8a8 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -47,7 +47,7 @@
 /**
  * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
  */
-public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
+final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
 
     private static final String LOG_TAG = TimeZoneDetectorService.TAG;
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 50d37f4..dddb11b 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -133,8 +133,8 @@
     }
 
     /**
-     * Adds a listener that will be called server flags related to this class change. The callbacks
-     * are delivered on the main looper thread.
+     * Adds a listener that will be called when server flags related to this class change. The
+     * callbacks are delivered on the main looper thread.
      *
      * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
      * method.
diff --git a/services/core/java/com/android/server/trust/OWNERS b/services/core/java/com/android/server/trust/OWNERS
index b039c4b..e2c6ce1 100644
--- a/services/core/java/com/android/server/trust/OWNERS
+++ b/services/core/java/com/android/server/trust/OWNERS
@@ -1 +1 @@
-include /core/java/android/app/trust/OWNERS
+include /core/java/android/service/trust/OWNERS
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 c8b7038..6d72999 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,
@@ -3434,6 +3470,7 @@
      */
     void cleanUp(boolean cleanServices, boolean setState) {
         task.cleanUpActivityReferences(this);
+        clearLastParentBeforePip();
 
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
@@ -5863,6 +5900,12 @@
             nowVisible = true;
             lastVisibleTime = SystemClock.uptimeMillis();
             mAtmService.scheduleAppGcsLocked();
+            // The nowVisible may be false in onAnimationFinished because the transition animation
+            // was started by starting window but the main window hasn't drawn so the procedure
+            // didn't schedule. Hence also check when nowVisible becomes true (drawn) to avoid the
+            // closing activity having to wait until idle timeout to be stopped or destroyed if the
+            // next activity won't report idle (e.g. repeated view animation).
+            mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
         }
     }
 
@@ -6679,26 +6722,26 @@
         // traverse the copy.
         final ArrayList<WindowState> children = new ArrayList<>(mChildren);
         children.forEach(WindowState::onExitAnimationDone);
+        // The starting window could transfer to another activity after app transition started, in
+        // that case the latest top activity might not receive exit animation done callback if the
+        // starting window didn't applied exit animation success. Notify animation finish to the
+        // starting window if needed.
+        if (task != null && startingMoved) {
+            final WindowState transferredStarting = task.getWindow(w ->
+                    w.mAttrs.type == TYPE_APPLICATION_STARTING);
+            if (transferredStarting != null && transferredStarting.mAnimatingExit
+                    && !transferredStarting.isSelfAnimating(0 /* flags */,
+                    ANIMATION_TYPE_WINDOW_ANIMATION)) {
+                transferredStarting.onExitAnimationDone();
+            }
+        }
 
         getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
         scheduleAnimation();
 
-        if (!mTaskSupervisor.mStoppingActivities.isEmpty()
-                || !mTaskSupervisor.mFinishingActivities.isEmpty()) {
-            if (mRootWindowContainer.allResumedActivitiesIdle()) {
-                // If all activities are already idle then we now need to make sure we perform
-                // the full stop of this activity. This is because we won't do that while they
-                // are still waiting for the animation to finish.
-                mTaskSupervisor.scheduleIdle();
-            } else if (mRootWindowContainer.allResumedActivitiesVisible()) {
-                // If all resumed activities are already visible (and should be drawn, see
-                // updateReportedVisibility ~ nowVisible) but not idle, we still schedule to
-                // process the stopping and finishing activities because the transition is done.
-                // This also avoids if the next activity never reports idle (e.g. animating view),
-                // the previous will need to wait until idle timeout to be stopped or destroyed.
-                mTaskSupervisor.scheduleProcessStoppingAndFinishingActivities();
-            }
-        }
+        // Schedule to handle the stopping and finishing activities which the animation is done
+        // because the activities which were animating have not been stopped yet.
+        mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
@@ -7022,11 +7065,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);
         }
 
@@ -7047,12 +7092,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.
@@ -7083,6 +7132,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.
@@ -7160,7 +7250,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);
         }
 
@@ -7212,12 +7305,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 */);
-        }
     }
 
     /**
@@ -7315,8 +7402,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();
@@ -7324,8 +7411,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();
@@ -7334,18 +7422,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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index bdde369..2b1cf39 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2027,7 +2027,9 @@
     }
 
     final void scheduleIdle() {
-        mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+        if (!mHandler.hasMessages(IDLE_NOW_MSG)) {
+            mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+        }
     }
 
     /**
@@ -2115,8 +2117,10 @@
         }
     }
 
-    void scheduleProcessStoppingAndFinishingActivities() {
-        if (!mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)) {
+    void scheduleProcessStoppingAndFinishingActivitiesIfNeeded() {
+        if ((!mStoppingActivities.isEmpty() || !mFinishingActivities.isEmpty())
+                && !mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)
+                && mRootWindowContainer.allResumedActivitiesVisible()) {
             mHandler.sendEmptyMessage(PROCESS_STOPPING_AND_FINISHING_MSG);
         }
     }
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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddcb2bf..cf9b88a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8517,20 +8517,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
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/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
index 14c02d5..0a5a3bf 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
@@ -20,11 +20,14 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainOwner;
 import android.content.pm.verify.domain.DomainVerificationManager;
 
 import com.android.server.pm.verify.domain.DomainVerificationService;
 
+import java.util.List;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.UUID;
 
 /**
@@ -58,4 +61,14 @@
             throws PackageManager.NameNotFoundException {
         return manager.setDomainVerificationUserSelection(domainSetId, domains, enabled);
     }
+
+    static SortedSet<DomainOwner> getOwnersForDomain(@NonNull DomainVerificationManager manager,
+            @Nullable String domain) {
+        return manager.getOwnersForDomain(domain);
+    }
+
+    static List<DomainOwner> getOwnersForDomain(@NonNull DomainVerificationService service,
+            @Nullable String domain, @UserIdInt int userId) {
+        return service.getOwnersForDomain(domain, userId);
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 7fea4ee..3838f68 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -292,8 +292,17 @@
         val manager0 = makeManager(service, 0)
         val manager1 = makeManager(service, 1)
 
-        assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
-        assertThat(manager0.getOwnersForDomain(DOMAIN_1)).isEmpty()
+        listOf(DOMAIN_1, "").forEach {
+            assertThat(service.getOwnersForDomain(it, 0)).isEmpty()
+            assertThat(manager0.getOwnersForDomain(it)).isEmpty()
+        }
+
+        assertFailsWith(NullPointerException::class) {
+            DomainVerificationJavaUtil.getOwnersForDomain(service, null, 0)
+        }
+        assertFailsWith(NullPointerException::class) {
+            DomainVerificationJavaUtil.getOwnersForDomain(manager0, null)
+        }
 
         assertThat(
             service.setStatus(
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 edfc21d..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 {
             }
@@ -2223,7 +2224,11 @@
     }
 
     @Test
-    public void minWindow() {
+    public void minWindowChangeEnabled() {
+        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);
 
@@ -2239,6 +2244,48 @@
     }
 
     @Test
+    public void minWindowChangeDisabled() {
+        doReturn(false).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++) {
+            final PendingIntent pi = getNewMockPendingIntent();
+            setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null);
+
+            assertEquals(1, mService.mAlarmStore.size());
+            final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+            assertEquals(window, a.windowLength);
+        }
+    }
+
+    @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/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index 924ad7f..76f7e80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -35,7 +35,6 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
 import android.util.LongSparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -68,7 +67,6 @@
     private File mBlobsDir;
 
     private LongSparseArray<BlobStoreSession> mUserSessions;
-    private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
 
     private static final String TEST_PKG1 = "com.example1";
     private static final String TEST_PKG2 = "com.example2";
@@ -99,10 +97,8 @@
         mHandler = new TestHandler(Looper.getMainLooper());
         mService = new BlobStoreManagerService(mContext, new TestInjector());
         mUserSessions = new LongSparseArray<>();
-        mUserBlobs = new ArrayMap<>();
 
         mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
-        mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
     }
 
     @After
@@ -147,7 +143,7 @@
         final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1,
                 blobHandle1, true /* hasLeases */);
         doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1);
-        mUserBlobs.put(blobHandle1, blobMetadata1);
+        addBlob(blobHandle1, blobMetadata1);
 
         final long blobId2 = 347;
         final File blobFile2 = mock(File.class);
@@ -156,7 +152,7 @@
         final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2,
                 blobHandle2, false /* hasLeases */);
         doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1);
-        mUserBlobs.put(blobHandle2, blobMetadata2);
+        addBlob(blobHandle2, blobMetadata2);
 
         final long blobId3 = 49875;
         final File blobFile3 = mock(File.class);
@@ -165,7 +161,7 @@
         final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3,
                 blobHandle3, true /* hasLeases */);
         doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1);
-        mUserBlobs.put(blobHandle3, blobMetadata3);
+        addBlob(blobHandle3, blobMetadata3);
 
         mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
                 blobId1, blobId2, blobId3);
@@ -197,10 +193,10 @@
         verify(blobMetadata2).destroy();
         verify(blobMetadata3).destroy();
 
-        assertThat(mUserBlobs.size()).isEqualTo(1);
-        assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
-        assertThat(mUserBlobs.get(blobHandle2)).isNull();
-        assertThat(mUserBlobs.get(blobHandle3)).isNull();
+        assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
+        assertThat(mService.getBlobForTest(blobHandle1)).isNotNull();
+        assertThat(mService.getBlobForTest(blobHandle2)).isNull();
+        assertThat(mService.getBlobForTest(blobHandle3)).isNull();
 
         assertThat(mService.getActiveIdsForTest()).containsExactly(
                 sessionId2, sessionId3, blobId1);
@@ -293,7 +289,7 @@
                 "label1", System.currentTimeMillis() - 2000, "tag1");
         final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1,
                 true /* hasLeases */);
-        mUserBlobs.put(blobHandle1, blobMetadata1);
+        addBlob(blobHandle1, blobMetadata1);
 
         final long blobId2 = 78974;
         final File blobFile2 = mock(File.class);
@@ -301,7 +297,7 @@
                 "label2", System.currentTimeMillis() + 30000, "tag2");
         final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2,
                 true /* hasLeases */);
-        mUserBlobs.put(blobHandle2, blobMetadata2);
+        addBlob(blobHandle2, blobMetadata2);
 
         final long blobId3 = 97;
         final File blobFile3 = mock(File.class);
@@ -309,7 +305,7 @@
                 "label3", System.currentTimeMillis() + 4400000, "tag3");
         final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3,
                 false /* hasLeases */);
-        mUserBlobs.put(blobHandle3, blobMetadata3);
+        addBlob(blobHandle3, blobMetadata3);
 
         mService.addActiveIdsForTest(blobId1, blobId2, blobId3);
 
@@ -321,8 +317,8 @@
         verify(blobMetadata2, never()).destroy();
         verify(blobMetadata3).destroy();
 
-        assertThat(mUserBlobs.size()).isEqualTo(1);
-        assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
+        assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
+        assertThat(mService.getBlobForTest(blobHandle2)).isNotNull();
 
         assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2);
         assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
@@ -336,21 +332,21 @@
         doReturn(size1).when(blobMetadata1).getSize();
         doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1);
         doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2);
-        mUserBlobs.put(mock(BlobHandle.class), blobMetadata1);
+        addBlob(mock(BlobHandle.class), blobMetadata1);
 
         final BlobMetadata blobMetadata2 = mock(BlobMetadata.class);
         final long size2 = 89475;
         doReturn(size2).when(blobMetadata2).getSize();
         doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1);
         doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2);
-        mUserBlobs.put(mock(BlobHandle.class), blobMetadata2);
+        addBlob(mock(BlobHandle.class), blobMetadata2);
 
         final BlobMetadata blobMetadata3 = mock(BlobMetadata.class);
         final long size3 = 328732;
         doReturn(size3).when(blobMetadata3).getSize();
         doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1);
         doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2);
-        mUserBlobs.put(mock(BlobHandle.class), blobMetadata3);
+        addBlob(mock(BlobHandle.class), blobMetadata3);
 
         // Verify usage is calculated correctly
         assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1))
@@ -388,6 +384,11 @@
         return blobMetadata;
     }
 
+    private void addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata) {
+        doReturn(blobHandle).when(blobMetadata).getBlobHandle();
+        mService.addBlobLocked(blobMetadata);
+    }
+
     private class TestHandler extends Handler {
         TestHandler(Looper looper) {
             super(looper);
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index 5bef877..e9b5b62 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -184,6 +184,7 @@
                 false /* callerInstantApp */,
                 null /* resolvedType */,
                 null /* requiredPermissions */,
+                null /* excludedPermissions */,
                 0 /* appOp */,
                 null /* options */,
                 new ArrayList<>(receivers), // Make a copy to not affect the original list.
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/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index e322ce5..96bab61 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -308,6 +308,8 @@
                 componentInfo,
                 type,
                 false /* resetLockoutRequiresHardwareAuthToken */));
+
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
     }
 
     private void setupFace(int id, boolean confirmationAlwaysRequired,
@@ -329,6 +331,6 @@
             }
         });
 
-        when(mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a5fbab5..ec3bea3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -378,7 +378,7 @@
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         // Disabled in user settings receives onError
-        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 null /* authenticators */);
         waitForIdle();
@@ -389,7 +389,7 @@
 
         // Enrolled, not disabled in settings, user requires confirmation in settings
         resetReceivers();
-        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
         when(mBiometricService.mSettingObserver.getConfirmationAlwaysRequired(
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(true);
@@ -1197,7 +1197,7 @@
     @Test
     public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
-        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
 
         // When only biometric is requested
@@ -1322,6 +1322,8 @@
 
         final int testId = 0;
 
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
                 .thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1536,7 +1538,7 @@
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
-        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
 
         if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
             when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
@@ -1564,7 +1566,7 @@
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
-        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+        when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
 
         assertEquals(modalities.length, strengths.length);
 
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..89798ce 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
@@ -5156,7 +5166,8 @@
 
         reset(mContext.spiedContext);
 
-        PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
+        PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+                "abcdXYZ5".getBytes(), /* isPin */ false);
 
         setActivePasswordState(passwordMetricsNoSymbols);
         assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5183,7 +5194,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 +5249,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 +6372,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 +6392,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 +7071,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/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 742f503..32fed3b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -248,21 +248,6 @@
         mStubbedTimeDetectorStrategy.verifyDumpCalled();
     }
 
-    @Test
-    public void testAutoTimeDetectionToggle() throws Exception {
-        mTimeDetectorService.handleAutoTimeDetectionChanged();
-        mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
-
-        mStubbedTimeDetectorStrategy.resetCallTracking();
-
-        mTimeDetectorService.handleAutoTimeDetectionChanged();
-        mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForMessagesToBeProcessed();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
-    }
-
     private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() {
         int slotIndex = 1234;
         TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
@@ -298,7 +283,6 @@
         private NetworkTimeSuggestion mLastNetworkSuggestion;
         private GnssTimeSuggestion mLastGnssSuggestion;
         private ExternalTimeSuggestion mLastExternalSuggestion;
-        private boolean mHandleAutoTimeDetectionChangedCalled;
         private boolean mDumpCalled;
 
         @Override
@@ -333,11 +317,6 @@
         }
 
         @Override
-        public void handleAutoTimeConfigChanged() {
-            mHandleAutoTimeDetectionChangedCalled = true;
-        }
-
-        @Override
         public void dump(IndentingPrintWriter pw, String[] args) {
             mDumpCalled = true;
         }
@@ -348,7 +327,6 @@
             mLastNetworkSuggestion = null;
             mLastGnssSuggestion = null;
             mLastExternalSuggestion = null;
-            mHandleAutoTimeDetectionChangedCalled = false;
             mDumpCalled = false;
         }
 
@@ -372,10 +350,6 @@
             assertEquals(expectedSuggestion, mLastExternalSuggestion);
         }
 
-        void verifyHandleAutoTimeDetectionChangedCalled() {
-            assertTrue(mHandleAutoTimeDetectionChangedCalled);
-        }
-
         void verifyDumpCalled() {
             assertTrue(mDumpCalled);
         }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 095703e..0d5b5a5 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -37,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +47,7 @@
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.util.Objects;
 
 @RunWith(AndroidJUnit4.class)
 public class TimeDetectorStrategyImplTest {
@@ -1133,11 +1135,17 @@
         private long mSystemClockMillis;
         private int mSystemClockUpdateThresholdMillis = 2000;
         private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY;
+        private ConfigurationChangeListener mConfigChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
 
         @Override
+        public void setConfigChangeListener(ConfigurationChangeListener listener) {
+            mConfigChangeListener = Objects.requireNonNull(listener);
+        }
+
+        @Override
         public int systemClockUpdateThresholdMillis() {
             return mSystemClockUpdateThresholdMillis;
         }
@@ -1230,6 +1238,7 @@
 
         void simulateAutoTimeZoneDetectionToggle() {
             mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
+            mConfigChangeListener.onChange();
         }
 
         void verifySystemClockNotSet() {
@@ -1330,7 +1339,6 @@
 
         Script simulateAutoTimeDetectionToggle() {
             mFakeEnvironment.simulateAutoTimeZoneDetectionToggle();
-            mTimeDetectorStrategy.handleAutoTimeConfigChanged();
             return this;
         }
 
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/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/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index f6d18fc..96e715e 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -637,18 +637,18 @@
             this.band = band;
             this.downlinkLowKhz = downlinkLowKhz;
             this.downlinkOffset = downlinkOffset;
+            this.downlinkRange = downlinkRange;
             this.uplinkLowKhz = uplinkLowKhz;
             this.uplinkOffset = uplinkOffset;
-            this.downlinkRange = downlinkRange;
             this.uplinkRange = uplinkRange;
         }
 
         int band;
         int downlinkLowKhz;
         int downlinkOffset;
+        int downlinkRange;
         int uplinkLowKhz;
         int uplinkOffset;
-        int downlinkRange;
         int uplinkRange;
     }
 
diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index f29f3bd..6b82045 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -598,7 +598,8 @@
                             : earfcnFrequency.downlinkOffset;
                     break;
                 } else {
-                    Log.e(TAG, "Band and the range of EARFCN are not consistent.");
+                    Rlog.w(TAG,"Band and the range of EARFCN are not consistent: band = " + band
+                            + " ,earfcn = " + earfcn + " ,isUplink = " + isUplink);
                     return INVALID_FREQUENCY;
                 }
             }
@@ -617,7 +618,7 @@
     }
 
     private static boolean isInEarfcnRange(int earfcn, EutranBandArfcnFrequency earfcnFrequency,
-                                           boolean isUplink) {
+            boolean isUplink) {
         if (isUplink) {
             return earfcn >= earfcnFrequency.uplinkOffset && earfcn <= earfcnFrequency.uplinkRange;
         } else {
@@ -640,7 +641,8 @@
                             : uarfcnFrequency.downlinkOffset;
                     break;
                 } else {
-                    Log.e(TAG, "Band and the range of UARFCN are not consistent.");
+                    Rlog.w(TAG,"Band and the range of UARFCN are not consistent: band = " + band
+                            + " ,uarfcn = " + uarfcn + " ,isUplink = " + isUplink);
                     return INVALID_FREQUENCY;
                 }
             }
@@ -716,7 +718,8 @@
                             arfcnOffset);
                     break;
                 } else {
-                    Log.e(TAG, "Band and the range of ARFCN are not consistent.");
+                    Rlog.w(TAG,"Band and the range of ARFCN are not consistent: band = " + band
+                            + " ,arfcn = " + arfcn + " ,isUplink = " + isUplink);
                     return INVALID_FREQUENCY;
                 }
             }
@@ -733,7 +736,7 @@
      * Downlink actual frequency(kHz) = Uplink actual frequency + 10
      */
     private static int convertArfcnToFrequency(int arfcn, int uplinkFrequencyFirstKhz,
-                                               int arfcnOffset) {
+            int arfcnOffset) {
         return uplinkFrequencyFirstKhz + 200 * (arfcn - arfcnOffset);
     }
 
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/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index ec85995..2d230a7 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -23,6 +23,7 @@
 import android.app.blob.LeaseInfo;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
@@ -56,6 +57,16 @@
         }
     }
 
+    public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input)
+            throws IOException {
+        try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
+            try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
+                    session.openWrite(0, -1))) {
+                FileUtils.copy(in, out);
+            }
+        }
+    }
+
     public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input,
             long lengthBytes) throws IOException {
         try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index c92d40c..db91eb2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.server.wm.flicker.close
 
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -44,6 +46,30 @@
             }
         }
 
+    @FlakyTest(bugId = 185401242)
+    @Test
+    override fun launcherLayerReplacesApp() {
+        super.launcherLayerReplacesApp()
+    }
+
+    @FlakyTest(bugId = 185401242)
+    @Test
+    override fun launcherReplacesAppWindowAsTopWindow() {
+        super.launcherReplacesAppWindowAsTopWindow()
+    }
+
+    @FlakyTest(bugId = 185401242)
+    @Test
+    override fun launcherWindowBecomesVisible() {
+        super.launcherWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 185401242)
+    @Test
+    override fun noUncoveredRegions() {
+        super.noUncoveredRegions()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 0bae8f6..3bd19ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -51,6 +51,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 185400889)
 class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 1c97be3..ad7ee30 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -98,6 +98,12 @@
         super.focusChanges()
     }
 
+    @FlakyTest(bugId = 185400889)
+    @Test
+    override fun noUncoveredRegions() {
+        super.noUncoveredRegions()
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 742a2d1..a353c59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.rotation
 
-import android.platform.test.annotations.Presubmit
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -69,13 +68,13 @@
         super.statusBarLayerIsAlwaysVisible()
     }
 
-    @FlakyTest
+    @FlakyTest(bugId = 185400889)
     @Test
     override fun noUncoveredRegions() {
         super.noUncoveredRegions()
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 185400889)
     @Test
     fun appLayerAlwaysVisible() {
         testSpec.assertLayers {
@@ -83,7 +82,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 185400889)
     @Test
     fun appLayerRotates() {
         testSpec.assertLayers {
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/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index d573e93..4f11669 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -19,6 +19,11 @@
     <!-- This test requires root to side load fs-verity cert. -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="EmojiRenderingTestApp.apk" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
new file mode 100644
index 0000000..ed34fa9
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
@@ -0,0 +1,32 @@
+// 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: "EmojiRenderingTestApp",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/AndroidManifest.xml b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5d8f5fc
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.emojirenderingtestapp">
+    <application>
+        <activity android:name=".EmojiRenderingTestActivity"/>
+    </application>
+</manifest>
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java
new file mode 100644
index 0000000..947e9c2
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.emojirenderingtestapp;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/** Test app to render an emoji. */
+public class EmojiRenderingTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        LinearLayout container = new LinearLayout(this);
+        container.setOrientation(LinearLayout.VERTICAL);
+        TextView textView = new TextView(this);
+        textView.setText("\uD83E\uDD72"); // 🥲
+        container.addView(textView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+        setContentView(container);
+    }
+}
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index e684556..032da3f 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -36,7 +36,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -47,6 +46,9 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
 
+    private static final String SYSTEM_FONTS_DIR = "/system/fonts/";
+    private static final String DATA_FONTS_DIR = "/data/fonts/files/";
+
     private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
 
     private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
@@ -72,6 +74,14 @@
     private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG =
             "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig";
 
+    private static final String EMOJI_RENDERING_TEST_APP_ID = "com.android.emojirenderingtestapp";
+    private static final String EMOJI_RENDERING_TEST_ACTIVITY =
+            EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity";
+
+    private interface ThrowingSupplier<T> {
+        T get() throws Exception;
+    }
+
     @Rule
     public final AddFsVerityCertRule mAddFsverityCertRule =
             new AddFsVerityCertRule(this, CERT_PATH);
@@ -91,7 +101,10 @@
         expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        assertThat(fontPath).startsWith("/data/fonts/files/");
+        assertThat(fontPath).startsWith(DATA_FONTS_DIR);
+        // The updated font should be readable and unmodifiable.
+        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+        expectRemoteCommandToFail("echo -n '' >> " + fontPath);
     }
 
     @Test
@@ -102,8 +115,12 @@
         expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
         String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        assertThat(fontPath2).startsWith("/data/fonts/files/");
+        assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
         assertThat(fontPath2).isNotEqualTo(fontPath);
+        // The new file should be readable.
+        expectRemoteCommandToSucceed("cat " + fontPath2 + " > /dev/null");
+        // The old file should be still readable.
+        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
     }
 
     @Test
@@ -119,25 +136,14 @@
         expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        assertThat(fontPath).startsWith("/data/fonts/files/");
+        assertThat(fontPath).startsWith(DATA_FONTS_DIR);
         assertThat(fontPath2).isNotEqualTo(fontPath);
-        assertThat(fontPath2).startsWith("/data/fonts/files/");
-        assertThat(fontPath3).startsWith("/data/fonts/files/");
+        assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
+        assertThat(fontPath3).startsWith(DATA_FONTS_DIR);
         assertThat(fontPath3).isNotEqualTo(fontPath);
     }
 
     @Test
-    public void updatedFont_dataFileIsImmutableAndReadable() throws Exception {
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
-                TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
-        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        assertThat(fontPath).startsWith("/data");
-
-        expectRemoteCommandToFail("echo -n '' >> " + fontPath);
-        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
-    }
-
-    @Test
     public void updateFont_invalidCert() throws Exception {
         expectRemoteCommandToFail(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
@@ -158,11 +164,37 @@
     }
 
     @Test
+    public void launchApp() throws Exception {
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR);
+        expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
+        expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+        waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
+                isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID));
+    }
+
+    @Test
+    public void launchApp_afterUpdateFont() throws Exception {
+        String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR);
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
+        String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR);
+        expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
+        expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+        // The original font should NOT be opened by the app.
+        waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
+                isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)
+                        && !isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID));
+    }
+
+    @Test
     public void reboot() throws Exception {
         expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        assertThat(fontPath).startsWith("/data/fonts/files/");
+        assertThat(fontPath).startsWith(DATA_FONTS_DIR);
 
         expectRemoteCommandToSucceed("stop");
         expectRemoteCommandToSucceed("start");
@@ -210,16 +242,40 @@
         });
     }
 
-    private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+    private void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
         long untilMillis = System.currentTimeMillis() + timeoutMillis;
         do {
-            if (func.get()) return;
             try {
+                if (func.get()) return;
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 throw new AssertionError("Interrupted", e);
+            } catch (Exception e) {
+                throw new AssertionError("Unexpected exception", e);
             }
         } while (System.currentTimeMillis() < untilMillis);
         throw new AssertionError("Timed out");
     }
+
+    private boolean isFileOpenedBy(String path, String appId) throws DeviceNotAvailableException {
+        String pid = pidOf(appId);
+        if (pid.isEmpty()) {
+            return false;
+        }
+        CommandResult result = getDevice().executeShellV2Command(
+                String.format("lsof -t -p %s '%s'", pid, path));
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            return false;
+        }
+        // The file is open if the output of lsof is non-empty.
+        return !result.getStdout().trim().isEmpty();
+    }
+
+    private String pidOf(String appId) throws DeviceNotAvailableException {
+        CommandResult result = getDevice().executeShellV2Command("pidof " + appId);
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            return "";
+        }
+        return result.getStdout().trim();
+    }
 }
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/integration/Android.bp b/tests/net/integration/Android.bp
index 730ef3b..39c424e 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -62,6 +62,7 @@
 // Utilities for testing framework code both in integration and unit tests.
 java_library {
     name: "frameworks-net-integration-testutils",
+    defaults: ["framework-connectivity-test-defaults"],
     srcs: ["util/**/*.java", "util/**/*.kt"],
     static_libs: [
         "androidx.annotation_annotation",
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 47e4b5e..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();
@@ -9768,7 +9768,8 @@
         return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(),
                 nc, new NetworkScore.Builder().setLegacyInt(0).build(),
                 mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
-                INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies());
+                INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker,
+                new ConnectivityService.Dependencies());
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 116d755e..36e229d 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -71,6 +71,8 @@
     static final int LOW_DAILY_LIMIT = 2;
     static final int HIGH_DAILY_LIMIT = 1000;
 
+    private static final int TEST_LINGER_DELAY_MS = 400;
+
     LingerMonitor mMonitor;
 
     @Mock ConnectivityService mConnService;
@@ -366,7 +368,7 @@
         NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
                 new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(),
                 mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd,
-                mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(),
+                mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
                 mQosCallbackTracker, new ConnectivityService.Dependencies());
         nai.everValidated = true;
         return nai;
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);
diff --git a/tools/hiddenapi/checksorted_sha.sh b/tools/hiddenapi/checksorted_sha.sh
index 451fed6..72fb867 100755
--- a/tools/hiddenapi/checksorted_sha.sh
+++ b/tools/hiddenapi/checksorted_sha.sh
@@ -1,10 +1,10 @@
 #!/bin/bash
 set -e
 LOCAL_DIR="$( dirname ${BASH_SOURCE} )"
-git show --name-only --pretty=format: $1 | grep "boot/hiddenapi/hiddenapi-.*txt" | while read file; do
+git show --name-only --pretty=format: $1 | grep "hiddenapi/hiddenapi-.*txt" | while read file; do
     diff <(git show $1:$file) <(git show $1:$file | $LOCAL_DIR/sort_api.sh )  || {
       echo -e "\e[1m\e[31m$file $1 is not sorted or contains duplicates. To sort it correctly:\e[0m"
-      echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $2/frameworks/base/$file\e[0m"
+      echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $PWD/$file\e[0m"
       exit 1
     }
 done
diff --git a/tools/hiddenapi/exclude.sh b/tools/hiddenapi/exclude.sh
index 822aba4..8b18f9b 100755
--- a/tools/hiddenapi/exclude.sh
+++ b/tools/hiddenapi/exclude.sh
@@ -10,7 +10,6 @@
   android.system \
   android.test \
   com.android.bouncycastle \
-  com.android.i18n.phonenumbers \
   com.android.okhttp \
   com.sun \
   dalvik \