Merge "Fix parsing in sensorprivacyservice" into sc-dev
diff --git a/Android.bp b/Android.bp
index 9655daf..240d803 100644
--- a/Android.bp
+++ b/Android.bp
@@ -369,6 +369,8 @@
         ":framework_native_aidl",
         ":gatekeeper_aidl",
         ":gsiservice_aidl",
+        ":idmap2_aidl",
+        ":idmap2_core_aidl",
         ":incidentcompanion_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
@@ -401,10 +403,12 @@
         ":framework-mediaprovider-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
+        ":framework-scheduling-sources",
         ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
         ":framework-wifi-updatable-sources",
+        ":ike-srcs",
         ":updatable-media-srcs",
     ],
     visibility: ["//visibility:private"],
@@ -413,12 +417,14 @@
 java_library {
     name: "framework-updatable-stubs-module_libs_api",
     static_libs: [
+        "android.net.ipsec.ike.stubs.module_lib",
         "framework-appsearch.stubs.module_lib",
         "framework-graphics.stubs.module_lib",
         "framework-media.stubs.module_lib",
         "framework-mediaprovider.stubs.module_lib",
         "framework-permission.stubs.module_lib",
         "framework-permission-s.stubs.module_lib",
+        "framework-scheduling.stubs.module_lib",
         "framework-sdkextensions.stubs.module_lib",
         "framework-statsd.stubs.module_lib",
         "framework-tethering.stubs.module_lib",
@@ -432,12 +438,14 @@
     name: "framework-all",
     installable: false,
     static_libs: [
+        "android.net.ipsec.ike.impl",
         "framework-minus-apex",
         "framework-appsearch.impl",
         "framework-graphics.impl",
         "framework-mediaprovider.impl",
         "framework-permission.impl",
         "framework-permission-s.impl",
+        "framework-scheduling.impl",
         "framework-sdkextensions.impl",
         "framework-statsd.impl",
         "framework-tethering.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 86364af..4bd524f 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -306,6 +306,7 @@
     name: "android_stubs_current",
     srcs: [ ":api-stubs-docs-non-updatable" ],
     static_libs: [
+        "android.net.ipsec.ike.stubs",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs",
@@ -314,6 +315,7 @@
         "framework-mediaprovider.stubs",
         "framework-permission.stubs",
         "framework-permission-s.stubs",
+        "framework-scheduling.stubs",
         "framework-sdkextensions.stubs",
         "framework-statsd.stubs",
         "framework-tethering.stubs",
@@ -328,6 +330,7 @@
     name: "android_system_stubs_current",
     srcs: [ ":system-api-stubs-docs-non-updatable" ],
     static_libs: [
+        "android.net.ipsec.ike.stubs.system",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs.system",
@@ -336,6 +339,7 @@
         "framework-mediaprovider.stubs.system",
         "framework-permission.stubs.system",
         "framework-permission-s.stubs.system",
+        "framework-scheduling.stubs.system",
         "framework-sdkextensions.stubs.system",
         "framework-statsd.stubs.system",
         "framework-tethering.stubs.system",
@@ -366,6 +370,7 @@
     static_libs: [
         // Modules do not have test APIs, but we want to include their SystemApis, like we include
         // the SystemApi of framework-non-updatable-sources.
+        "android.net.ipsec.ike.stubs.system",
         "art.module.public.api.stubs",
         "conscrypt.module.public.api.stubs",
         "framework-appsearch.stubs.system",
@@ -374,6 +379,7 @@
         "framework-mediaprovider.stubs.system",
         "framework-permission.stubs.system",
         "framework-permission-s.stubs.system",
+        "framework-scheduling.stubs.system",
         "framework-sdkextensions.stubs.system",
         "framework-statsd.stubs.system",
         "framework-tethering.stubs.system",
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 97cfe36..cd75b14 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -37,24 +37,36 @@
 public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelable {
     @NonNull private final Map<KeyType, ValueType> mSuccesses;
     @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mFailures;
+    @NonNull private final Map<KeyType, AppSearchResult<ValueType>> mAll;
 
     AppSearchBatchResult(
             @NonNull Map<KeyType, ValueType> successes,
-            @NonNull Map<KeyType, AppSearchResult<ValueType>> failures) {
+            @NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
+            @NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
         mSuccesses = successes;
         mFailures = failures;
+        mAll = all;
     }
 
     private AppSearchBatchResult(@NonNull Parcel in) {
-        mSuccesses = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
-        mFailures = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+        mAll = Collections.unmodifiableMap(in.readHashMap(/*loader=*/ null));
+        Map<KeyType, ValueType> successes = new ArrayMap<>();
+        Map<KeyType, AppSearchResult<ValueType>> failures = new ArrayMap<>();
+        for (Map.Entry<KeyType, AppSearchResult<ValueType>> entry : mAll.entrySet()) {
+            if (entry.getValue().isSuccess()) {
+                successes.put(entry.getKey(), entry.getValue().getResultValue());
+            } else {
+                failures.put(entry.getKey(), entry.getValue());
+            }
+        }
+        mSuccesses = Collections.unmodifiableMap(successes);
+        mFailures = Collections.unmodifiableMap(failures);
     }
 
     /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeMap(mSuccesses);
-        dest.writeMap(mFailures);
+        dest.writeMap(mAll);
     }
 
     /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
@@ -63,8 +75,8 @@
     }
 
     /**
-     * Returns a {@link Map} of all successful keys mapped to the successful
-     * {@link AppSearchResult}s they produced.
+     * Returns a {@link Map} of all successful keys mapped to the successful {@link
+     * AppSearchResult}s they produced.
      *
      * <p>The values of the {@link Map} will not be {@code null}.
      */
@@ -85,7 +97,19 @@
     }
 
     /**
+     * Returns a {@link Map} of all keys mapped to the {@link AppSearchResult}s they produced.
+     *
+     * <p>The values of the {@link Map} will not be {@code null}.
+     * @hide
+     */
+    @NonNull
+    public Map<KeyType, AppSearchResult<ValueType>> getAll() {
+        return mAll;
+    }
+
+    /**
      * Asserts that this {@link AppSearchBatchResult} has no failures.
+     *
      * @hide
      */
     public void checkSuccess() {
@@ -133,6 +157,7 @@
     public static final class Builder<KeyType, ValueType> {
         private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>();
         private final Map<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();
+        private final Map<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
         private boolean mBuilt = false;
 
         /**
@@ -181,6 +206,7 @@
                 mFailures.put(key, result);
                 mSuccesses.remove(key);
             }
+            mAll.put(key, result);
             return this;
         }
 
@@ -189,7 +215,7 @@
         public AppSearchBatchResult<KeyType, ValueType> build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mBuilt = true;
-            return new AppSearchBatchResult<>(mSuccesses, mFailures);
+            return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
         }
     }
 }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index 76225e4..440f633 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -22,6 +22,7 @@
 import android.app.appsearch.exceptions.AppSearchException;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -35,19 +36,21 @@
  */
 public final class AppSearchResult<ValueType> implements Parcelable {
     /**
-     * Result codes from {@link AppSearchManager} methods.
+     * Result codes from {@link AppSearchSession} methods.
+     *
      * @hide
      */
-    @IntDef(value = {
-            RESULT_OK,
-            RESULT_UNKNOWN_ERROR,
-            RESULT_INTERNAL_ERROR,
-            RESULT_INVALID_ARGUMENT,
-            RESULT_IO_ERROR,
-            RESULT_OUT_OF_SPACE,
-            RESULT_NOT_FOUND,
-            RESULT_INVALID_SCHEMA,
-    })
+    @IntDef(
+            value = {
+                RESULT_OK,
+                RESULT_UNKNOWN_ERROR,
+                RESULT_INTERNAL_ERROR,
+                RESULT_INVALID_ARGUMENT,
+                RESULT_IO_ERROR,
+                RESULT_OUT_OF_SPACE,
+                RESULT_NOT_FOUND,
+                RESULT_INVALID_SCHEMA,
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -60,21 +63,21 @@
     /**
      * An internal error occurred within AppSearch, which the caller cannot address.
      *
-     * This error may be considered similar to {@link IllegalStateException}
+     * <p>This error may be considered similar to {@link IllegalStateException}
      */
     public static final int RESULT_INTERNAL_ERROR = 2;
 
     /**
      * The caller supplied invalid arguments to the call.
      *
-     * This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
      */
     public static final int RESULT_INVALID_ARGUMENT = 3;
 
     /**
      * An issue occurred reading or writing to storage. The call might succeed if repeated.
      *
-     * This error may be considered similar to {@link java.io.IOException}.
+     * <p>This error may be considered similar to {@link java.io.IOException}.
      */
     public static final int RESULT_IO_ERROR = 4;
 
@@ -127,7 +130,7 @@
     /**
      * Returns the result value associated with this result, if it was successful.
      *
-     * <p>See the documentation of the particular {@link AppSearchManager} call producing this
+     * <p>See the documentation of the particular {@link AppSearchSession} call producing this
      * {@link AppSearchResult} for what is placed in the result value by that call.
      *
      * @throws IllegalStateException if this {@link AppSearchResult} is not successful.
@@ -145,8 +148,8 @@
      *
      * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error
      * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the
-     * documentation of the particular {@link AppSearchManager} call producing this
-     * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}.
+     * documentation of the particular {@link AppSearchSession} call producing this {@link
+     * AppSearchResult} for what is returned by {@link #getErrorMessage}.
      */
     @Nullable
     public String getErrorMessage() {
@@ -205,6 +208,7 @@
 
     /**
      * Creates a new successful {@link AppSearchResult}.
+     *
      * @hide
      */
     @NonNull
@@ -215,6 +219,7 @@
 
     /**
      * Creates a new failed {@link AppSearchResult}.
+     *
      * @hide
      */
     @NonNull
@@ -227,6 +232,8 @@
     @NonNull
     public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
             @NonNull Throwable t) {
+        Log.d("AppSearchResult", "Converting throwable to failed result.", t);
+
         if (t instanceof AppSearchException) {
             return ((AppSearchException) t).toAppSearchResult();
         }
@@ -241,6 +248,6 @@
         } else {
             resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
         }
-        return AppSearchResult.newFailedResult(resultCode, t.toString());
+        return AppSearchResult.newFailedResult(resultCode, 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 8723515..73ca0cc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -108,40 +108,53 @@
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
      * types of schema modifications are always safe and are made without deleting any existing
      * documents:
+     *
      * <ul>
-     *     <li>Addition of new types
-     *     <li>Addition of new
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
-     *         type
-     *     <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
+     *   <li>Addition of new types
+     *   <li>Addition of new {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} or
+     *       {@link AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} properties to a
+     *       type
+     *   <li>Changing the cardinality of a data type to be less restrictive (e.g. changing an {@link
+     *       AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+     *       AppSearchSchema.PropertyConfig#CARDINALITY_REPEATED REPEATED} property.
      * </ul>
      *
      * <p>The following types of schema changes are not backwards-compatible:
-     * <ul>
-     *     <li>Removal of an existing type
-     *     <li>Removal of a property from a type
-     *     <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
-     *     <li>For properties of {@code Document} type, changing the schema type of
-     *         {@code Document}s of that property
-     *     <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
-     *     <li>Adding a
-     *         {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
-     * </ul>
-     * <p>Supplying a schema with such changes will, by default, result in this call returning an
-     * {@link AppSearchResult} with a code of {@link AppSearchResult#RESULT_INVALID_SCHEMA} and an
-     * error message describing the incompatibility. In this case the previously set schema will
-     * remain active.
      *
-     * <p>If you need to make non-backwards-compatible changes as described above, you can set the
-     * {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In this case,
-     * instead of returning an {@link AppSearchResult} with the
-     * {@link AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
-     * compatible with the new schema will be deleted and the incompatible schema will be applied.
+     * <ul>
+     *   <li>Removal of an existing type
+     *   <li>Removal of a property from a type
+     *   <li>Changing the data type ({@code boolean}, {@code long}, etc.) of an existing property
+     *   <li>For properties of {@code Document} type, changing the schema type of {@code Document}s
+     *       of that property
+     *   <li>Changing the cardinality of a data type to be more restrictive (e.g. changing an {@link
+     *       AppSearchSchema.PropertyConfig#CARDINALITY_OPTIONAL OPTIONAL} property into a {@link
+     *       AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property).
+     *   <li>Adding a {@link AppSearchSchema.PropertyConfig#CARDINALITY_REQUIRED REQUIRED} property.
+     * </ul>
+     *
+     * <p>Supplying a schema with such changes will, by default, result in this call completing its
+     * future with an {@link android.app.appsearch.exceptions.AppSearchException} with a code of
+     * {@link AppSearchResult#RESULT_INVALID_SCHEMA} and a message describing the incompatibility.
+     * In this case the previously set schema will remain active.
+     *
+     * <p>If you need to make non-backwards-compatible changes as described above, you can either:
+     *
+     * <ul>
+     *   <li>Set the {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. In
+     *       this case, instead of completing its future with an {@link
+     *       android.app.appsearch.exceptions.AppSearchException} with the {@link
+     *       AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
+     *       compatible with the new schema will be deleted and the incompatible schema will be
+     *       applied. Incompatible types and deleted types will be set into {@link
+     *       SetSchemaResponse#getIncompatibleTypes()} and {@link
+     *       SetSchemaResponse#getDeletedTypes()}, respectively.
+     *   <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
+     *       and make no deletion. The migrator will migrate documents from it's old schema version
+     *       to the new version. Migrated types will be set into both {@link
+     *       SetSchemaResponse#getIncompatibleTypes()} and {@link
+     *       SetSchemaResponse#getMigratedTypes()}. See the migration section below.
+     * </ul>
      *
      * <p>It is a no-op to set the same schema as has been previously set; this is handled
      * efficiently.
@@ -149,16 +162,32 @@
      * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
      * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
      * visibility settings apply only to the schemas that are included in the {@code request}.
-     * Visibility settings for a schema type do not apply or persist across
-     * {@link SetSchemaRequest}s.
+     * Visibility settings for a schema type do not apply or persist across {@link
+     * SetSchemaRequest}s.
      *
-     * @param request  The schema update request.
+     * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
+     * schema. You can save your documents by setting {@link
+     * android.app.appsearch.AppSearchSchema.Migrator} via the {@link
+     * SetSchemaRequest.Builder#setMigrator} for each type you want to save.
+     *
+     * <p>{@link android.app.appsearch.AppSearchSchema.Migrator#onDowngrade} or {@link
+     * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} will be triggered if the version
+     * number of the schema stored in AppSearch is different with the version in the request.
+     *
+     * <p>If any error or Exception occurred in the {@link
+     * android.app.appsearch.AppSearchSchema.Migrator#onDowngrade}, {@link
+     * android.app.appsearch.AppSearchSchema.Migrator#onUpgrade} or {@link
+     * android.app.appsearch.AppSearchMigrationHelper.Transformer#transform}, the migration will be
+     * terminated, the setSchema request will be rejected unless the schema changes are
+     * backwards-compatible, and stored documents won't have any observable changes.
+     *
+     * @param request The schema update request.
      * @param executor Executor on which to invoke the callback.
      * @param callback Callback to receive errors resulting from setting the schema. If the
      *                 operation succeeds, the callback will be invoked with {@code null}.
+     * @see android.app.appsearch.AppSearchSchema.Migrator
+     * @see android.app.appsearch.AppSearchMigrationHelper.Transformer
      */
-    // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
-    //  exposed.
     public void setSchema(
             @NonNull SetSchemaRequest request,
             @NonNull @CallbackExecutor Executor executor,
@@ -195,11 +224,9 @@
                             executor.execute(() -> {
                                 if (result.isSuccess()) {
                                     callback.accept(
-                                            // TODO(b/151178558) implement Migration in platform.
+                                            // TODO(b/177266929) implement Migration in platform.
                                             AppSearchResult.newSuccessfulResult(
-                                                    new SetSchemaResponse.Builder().setResultCode(
-                                                            result.getResultCode())
-                                                            .build()));
+                                                    new SetSchemaResponse.Builder().build()));
                                 } else {
                                     callback.accept(result);
                                 }
@@ -258,7 +285,7 @@
      * <p>Each {@link GenericDocument}'s {@code schemaType} field must be set to the name of a
      * schema type previously registered via the {@link #setSchema} method.
      *
-     * @param request  {@link PutDocumentsRequest} containing documents to be indexed
+     * @param request {@link PutDocumentsRequest} containing documents to be indexed
      * @param executor Executor on which to invoke the callback.
      * @param callback Callback to receive pending result of performing this operation. The keys
      *                 of the returned {@link AppSearchBatchResult} are the URIs of the input
@@ -299,9 +326,10 @@
     }
 
     /**
-     * Retrieves {@link GenericDocument}s by URI.
+     * Gets {@link GenericDocument} objects by URIs and namespace from the {@link AppSearchSession}
+     * database.
      *
-     * @param request  {@link GetByUriRequest} containing URIs to be retrieved.
+     * @param request a request containing URIs and namespace to get documents for.
      * @param executor Executor on which to invoke the callback.
      * @param callback Callback to receive the pending result of performing this operation. The keys
      *                 of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -379,48 +407,65 @@
     }
 
     /**
-     * Searches a document based on a given query string.
+     * Retrieves documents from the open {@link AppSearchSession} that match a given query string
+     * and type of search provided.
      *
-     * <p>Currently we support following features in the raw query format:
+     * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
+     * and operators.
+     *
+     * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
+     * returned.
+     *
+     * <p>For query strings with a single term and no operators, documents that match the provided
+     * query string and {@link SearchSpec} will be returned.
+     *
+     * <p>The following operators are supported:
+     *
      * <ul>
-     *     <li>AND
-     *     <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
-     *     ‘cat’”).
-     *     Example: hello world matches documents that have both ‘hello’ and ‘world’
-     *     <li>OR
-     *     <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
-     *     ‘cat’”).
-     *     Example: dog OR puppy
-     *     <li>Exclusion
-     *     <p>Exclude a term (e.g. “match documents that do
-     *     not have the term ‘dog’”).
-     *     Example: -dog excludes the term ‘dog’
-     *     <li>Grouping terms
-     *     <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
-     *     “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
-     *     Example: (dog puppy) (cat kitten) two one group containing two terms.
-     *     <li>Property restricts
-     *     <p> Specifies which properties of a document to specifically match terms in (e.g.
-     *     “match documents where the ‘subject’ property contains ‘important’”).
-     *     Example: subject:important matches documents with the term ‘important’ in the
-     *     ‘subject’ property
-     *     <li>Schema type restricts
-     *     <p>This is similar to property restricts, but allows for restricts on top-level document
-     *     fields, such as schema_type. Clients should be able to limit their query to documents of
-     *     a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
-     *     Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
-     *     that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
-     *     ‘Video’ schema type.
+     *   <li>AND (implicit)
+     *       <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
+     *       <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
+     *       including "AND" in a query string will treat "AND" as a term, returning documents that
+     *       also contain "AND".
+     *       <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
+     *       "banana".
+     *       <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
+     *       <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
+     *       "cherry".
+     *   <li>OR
+     *       <p>OR is an operator that matches documents that contain <i>any</i> provided term.
+     *       <p>Example: "apple OR banana" matches documents that contain either "apple" or
+     *       "banana".
+     *       <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
+     *       "banana", or "cherry".
+     *   <li>Exclusion (-)
+     *       <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
+     *       provided term.
+     *       <p>Example: "-apple" matches documents that do not contain "apple".
+     *   <li>Grouped Terms
+     *       <p>For queries that require multiple operators and terms, terms can be grouped into
+     *       subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
+     *       <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
+     *       "donut" or "bagel" and either "coffee" or "tea".
+     *   <li>Property Restricts
+     *       <p>For queries that require a term to match a specific {@link AppSearchSchema} property
+     *       of a document, a ":" must be included between the property name and the term.
+     *       <p>Example: "subject:important" matches documents that contain the term "important" in
+     *       the "subject" property.
      * </ul>
      *
-     * <p> This method is lightweight. The heavy work will be done in
-     * {@link SearchResults#getNextPage}.
+     * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
+     * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
      *
-     * @param queryExpression Query String to search.
-     * @param searchSpec      Spec for setting filters, raw query etc.
+     * <p>This method is lightweight. The heavy work will be done in {@link
+     * SearchResults#getNextPage}.
+     *
+     * @param queryExpression query string to search.
+     * @param searchSpec spec for setting document filters, adding projection, setting term match
+     *     type, etc.
      * @param executor        Executor on which to invoke the callback of the following request
      *                        {@link SearchResults#getNextPage}.
-     * @return The search result of performing this operation.
+     * @return a {@link SearchResults} object for retrieved matched documents.
      */
     @NonNull
     public SearchResults search(
@@ -442,8 +487,8 @@
      *
      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
      * metrics for that particular document. These metrics are used for ordering {@link #search}
-     * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
-     * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
+     * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
+     * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
      *
      * <p>Reporting usage of a document is optional.
      *
@@ -481,9 +526,17 @@
     }
 
     /**
-     * Removes {@link GenericDocument}s from the index by URI.
+     * Removes {@link GenericDocument} objects by URIs and namespace from the {@link
+     * AppSearchSession} database.
      *
-     * @param request  Request containing URIs to be removed.
+     * <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.
+     *
+     * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
      * @param executor Executor on which to invoke the callback.
      * @param callback Callback to receive the pending result of performing this operation. The keys
      *                 of the returned {@link AppSearchBatchResult} are the input URIs. The values
@@ -522,19 +575,18 @@
 
     /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
-     * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
-     * {@link SearchSpec.Builder#addFilterNamespaces} and
-     * {@link SearchSpec.Builder#addFilterSchemas}.
+     * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
+     * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
      *
-     * <p> An empty {@code queryExpression} matches all documents.
+     * <p>An empty {@code queryExpression} matches all documents.
      *
-     * <p> An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in
-     * the current database.
+     * <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
+     * current database.
      *
      * @param queryExpression Query String to search.
-     * @param searchSpec      Spec containing schemaTypes, namespaces and query expression indicates
-     *                        how document will be removed. All specific about how to scoring,
-     *                        ordering, snippeting and resulting will be ignored.
+     * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
+     *     document will be removed. All specific about how to scoring, ordering, snippeting and
+     *     resulting will be ignored.
      * @param executor        Executor on which to invoke the callback.
      * @param callback        Callback to receive errors resulting from removing the documents. If
      *                        the operation succeeds, the callback will be invoked with
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index 8651834..09bca4f 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -32,7 +32,7 @@
 /**
  * This class provides global access to the centralized AppSearch index maintained by the system.
  *
- * <p>Apps can retrieve indexed documents through the query API.
+ * <p>Apps can retrieve indexed documents through the {@link #search} API.
  */
 public class GlobalSearchSession implements Closeable {
 
@@ -90,48 +90,26 @@
     }
 
     /**
-     * Searches across all documents in the storage based on a given query string.
+     * Retrieves documents from all AppSearch databases that the querying application has access to.
      *
-     * <p>Currently we support following features in the raw query format:
-     * <ul>
-     *     <li>AND
-     *     <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and
-     *     ‘cat’”).
-     *     Example: hello world matches documents that have both ‘hello’ and ‘world’
-     *     <li>OR
-     *     <p>OR joins (e.g. “match documents that have either the term ‘dog’ or
-     *     ‘cat’”).
-     *     Example: dog OR puppy
-     *     <li>Exclusion
-     *     <p>Exclude a term (e.g. “match documents that do
-     *     not have the term ‘dog’”).
-     *     Example: -dog excludes the term ‘dog’
-     *     <li>Grouping terms
-     *     <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
-     *     “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
-     *     Example: (dog puppy) (cat kitten) two one group containing two terms.
-     *     <li>Property restricts
-     *     <p> Specifies which properties of a document to specifically match terms in (e.g.
-     *     “match documents where the ‘subject’ property contains ‘important’”).
-     *     Example: subject:important matches documents with the term ‘important’ in the
-     *     ‘subject’ property
-     *     <li>Schema type restricts
-     *     <p>This is similar to property restricts, but allows for restricts on top-level document
-     *     fields, such as schema_type. Clients should be able to limit their query to documents of
-     *     a certain schema_type (e.g. “match documents that are of the ‘Email’ schema_type”).
-     *     Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will match documents
-     *     that contain the query term ‘dog’ and are of either the ‘Email’ schema type or the
-     *     ‘Video’ schema type.
-     * </ul>
+     * <p>Applications can be granted access to documents by specifying {@link
+     * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} when building a schema.
      *
-     * <p> This method is lightweight. The heavy work will be done in
-     * {@link SearchResults#getNextPage}.
+     * <p>Document access can also be granted to system UIs by specifying {@link
+     * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} when building a schema.
      *
-     * @param queryExpression Query String to search.
-     * @param searchSpec      Spec for setting filters, raw query etc.
+     * <p>See {@link AppSearchSession#search} for a detailed explanation on
+     * forming a query string.
+     *
+     * <p>This method is lightweight. The heavy work will be done in {@link
+     * SearchResults#getNextPage}.
+     *
+     * @param queryExpression query string to search.
+     * @param searchSpec spec for setting document filters, adding projection, setting term match
+     *     type, etc.
      * @param executor        Executor on which to invoke the callback of the following request
      *                        {@link SearchResults#getNextPage}.
-     * @return The search result of performing this operation.
+     * @return a {@link SearchResults} object for retrieved matched documents.
      */
     @NonNull
     public SearchResults search(
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
index 704509b..a63e015 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java
@@ -35,8 +35,8 @@
 /**
  * SearchResults are a returned object from a query API.
  *
- * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets
- * based on request.
+ * <p>Each {@link SearchResult} contains a document and may contain other fields like snippets based
+ * on request.
  *
  * <p>Should close this object after finish fetching results.
  *
@@ -89,8 +89,8 @@
     /**
      * Gets a whole page of {@link SearchResult}s.
      *
-     * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an
-     * empty list.
+     * <p>Re-call this method to get next page of {@link SearchResult}, until it returns an empty
+     * list.
      *
      * <p>The page size is set by {@link SearchSpec.Builder#setResultCountPerPage}.
      *
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 e94b3b2..8bf438d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -90,6 +90,7 @@
      * <p>This method creates a new list when called.
      */
     @NonNull
+    @SuppressWarnings("MixedMutabilityReturnType")
     public List<PropertyConfig> getProperties() {
         ArrayList<Bundle> propertyBundles =
                 mBundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD);
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 4c11514..138eb23 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -36,7 +36,8 @@
 /**
  * Represents a document unit.
  *
- * <p>Documents are constructed via {@link GenericDocument.Builder}.
+ * <p>Documents contain structured data conforming to their {@link AppSearchSchema} type. Each
+ * document is uniquely identified by a URI and namespace.
  *
  * @see AppSearchSession#put
  * @see AppSearchSession#getByUri
@@ -48,16 +49,10 @@
     /** The default empty namespace. */
     public static final String DEFAULT_NAMESPACE = "";
 
-    /**
-     * The maximum number of elements in a repeatable field. Will reject the request if exceed this
-     * limit.
-     */
+    /** The maximum number of elements in a repeatable field. */
     private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
 
-    /**
-     * The maximum {@link String#length} of a {@link String} field. Will reject the request if
-     * {@link String}s longer than this.
-     */
+    /** The maximum {@link String#length} of a {@link String} field. */
     private static final int MAX_STRING_LENGTH = 20_000;
 
     /** The maximum number of indexed properties a document can have. */
@@ -149,7 +144,7 @@
         return mBundle.getString(NAMESPACE_FIELD, DEFAULT_NAMESPACE);
     }
 
-    /** Returns the schema type of the {@link GenericDocument}. */
+    /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
     @NonNull
     public String getSchemaType() {
         return mSchemaType;
@@ -165,14 +160,14 @@
     }
 
     /**
-     * Returns the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+     * Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
      *
      * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
      * {@code creationTimestampMillis + ttlMillis}, measured in the {@link System#currentTimeMillis}
      * time base, the document will be auto-deleted.
      *
      * <p>The default value is 0, which means the document is permanent and won't be auto-deleted
-     * until the app is uninstalled.
+     * until the app is uninstalled or {@link AppSearchSession#remove} is called.
      */
     public long getTtlMillis() {
         return mBundle.getLong(TTL_MILLIS_FIELD, DEFAULT_TTL_MILLIS);
@@ -182,12 +177,12 @@
      * Returns the score of the {@link GenericDocument}.
      *
      * <p>The score is a query-independent measure of the document's quality, relative to other
-     * {@link GenericDocument}s of the same type.
+     * {@link GenericDocument} objects of the same {@link AppSearchSchema} type.
      *
      * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
      * Documents with higher scores are considered better than documents with lower scores.
      *
-     * <p>Any nonnegative integer can be used a score.
+     * <p>Any non-negative integer can be used a score.
      */
     public int getScore() {
         return mBundle.getInt(SCORE_FIELD, DEFAULT_SCORE);
@@ -355,7 +350,7 @@
     }
 
     /**
-     * Retrieves a repeated {@link String} property by key.
+     * Retrieves a repeated {@code long[]} property by key.
      *
      * @param key The key to look for.
      * @return The {@code long[]} associated with the given key, or {@code null} if no value is set
@@ -580,14 +575,17 @@
         private boolean mBuilt = false;
 
         /**
-         * Create a new {@link GenericDocument.Builder}.
+         * Creates a new {@link GenericDocument.Builder}.
          *
-         * @param uri The uri of {@link GenericDocument}.
-         * @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
-         *     schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
-         *     inserting a document of this {@code schemaType} into the AppSearch index using {@link
-         *     AppSearchSession#put}. Otherwise, the document will be rejected by {@link
-         *     AppSearchSession#put}.
+         * <p>Once {@link #build} is called, the instance can no longer be used.
+         *
+         * @param uri the URI to set for the {@link GenericDocument}.
+         * @param schemaType the {@link AppSearchSchema} type of the {@link GenericDocument}. The
+         *     provided {@code schemaType} must be defined using {@link AppSearchSession#setSchema}
+         *     prior to inserting a document of this {@code schemaType} into the AppSearch index
+         *     using {@link AppSearchSession#put}. Otherwise, the document will
+         *     be rejected by {@link AppSearchSession#put} with result code
+         *     {@link AppSearchResult#RESULT_NOT_FOUND}.
          */
         @SuppressWarnings("unchecked")
         public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -606,15 +604,18 @@
         }
 
         /**
-         * Sets the app-defined namespace this Document resides in. No special values are reserved
+         * Sets the app-defined namespace this document resides in. No special values are reserved
          * or understood by the infrastructure.
          *
          * <p>URIs are unique within a namespace.
          *
          * <p>The number of namespaces per app should be kept small for efficiency reasons.
+         *
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setNamespace(@NonNull String namespace) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
             mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
             return mBuilderTypeInstance;
         }
@@ -623,14 +624,15 @@
          * Sets the score of the {@link GenericDocument}.
          *
          * <p>The score is a query-independent measure of the document's quality, relative to other
-         * {@link GenericDocument}s of the same type.
+         * {@link GenericDocument} objects of the same {@link AppSearchSchema} type.
          *
          * <p>Results may be sorted by score using {@link SearchSpec.Builder#setRankingStrategy}.
          * Documents with higher scores are considered better than documents with lower scores.
          *
-         * <p>Any nonnegative integer can be used a score.
+         * <p>Any non-negative integer can be used a score. By default, scores are set to 0.
          *
-         * @throws IllegalArgumentException If the provided value is negative.
+         * @param score any non-negative {@code int} representing the document's score.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
@@ -645,8 +647,11 @@
         /**
          * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
          *
-         * <p>Should be set using a value obtained from the {@link System#currentTimeMillis} time
-         * base.
+         * <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
+         * time base.
+         *
+         * @param creationTimestampMillis a creation timestamp in milliseconds.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setCreationTimestampMillis(long creationTimestampMillis) {
@@ -657,17 +662,17 @@
         }
 
         /**
-         * Sets the TTL (Time To Live) of the {@link GenericDocument}, in milliseconds.
+         * Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
          *
          * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
          * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
          * System#currentTimeMillis} time base, the document will be auto-deleted.
          *
          * <p>The default value is 0, which means the document is permanent and won't be
-         * auto-deleted until the app is uninstalled.
+         * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
          *
-         * @param ttlMillis A non-negative duration in milliseconds.
-         * @throws IllegalArgumentException If the provided value is negative.
+         * @param ttlMillis a non-negative duration in milliseconds.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setTtlMillis(long ttlMillis) {
@@ -682,8 +687,11 @@
         /**
          * Sets one or multiple {@code String} values for a property, replacing its previous values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@code String} values of the property.
+         * @param key the key associated with the {@code values}.
+         * @param values the {@code String} values of the property.
+         * @throws IllegalArgumentException if no values are provided, if provided values exceed
+         *     maximum repeated property length, or if a passed in {@code String} is {@code null}.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyString(@NonNull String key, @NonNull String... values) {
@@ -698,8 +706,11 @@
          * Sets one or multiple {@code boolean} values for a property, replacing its previous
          * values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@code boolean} values of the property.
+         * @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 IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyBoolean(@NonNull String key, @NonNull boolean... values) {
@@ -713,8 +724,11 @@
         /**
          * Sets one or multiple {@code long} values for a property, replacing its previous values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@code long} values of the property.
+         * @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 IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyLong(@NonNull String key, @NonNull long... values) {
@@ -728,8 +742,11 @@
         /**
          * Sets one or multiple {@code double} values for a property, replacing its previous values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@code double} values of the property.
+         * @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 IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyDouble(@NonNull String key, @NonNull double... values) {
@@ -743,8 +760,11 @@
         /**
          * Sets one or multiple {@code byte[]} for a property, replacing its previous values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@code byte[]} of the property.
+         * @param key the key associated with the {@code values}.
+         * @param values the {@code byte[]} of the property.
+         * @throws IllegalArgumentException if no values are provided, if provided values exceed
+         *     maximum repeated property length, or if a passed in {@code byte[]} is {@code null}.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyBytes(@NonNull String key, @NonNull byte[]... values) {
@@ -759,8 +779,12 @@
          * Sets one or multiple {@link GenericDocument} values for a property, replacing its
          * previous values.
          *
-         * @param key The key associated with the {@code values}.
-         * @param values The {@link GenericDocument} values of the property.
+         * @param key the key associated with the {@code values}.
+         * @param values the {@link GenericDocument} values of the property.
+         * @throws IllegalArgumentException if no values are provided, if provided values exceed if
+         *     provided values exceed maximum repeated property length, or if a passed in {@link
+         *     GenericDocument} is {@code null}.
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public BuilderType setPropertyDocument(
@@ -853,7 +877,11 @@
             }
         }
 
-        /** Builds the {@link GenericDocument} object. */
+        /**
+         * Builds the {@link GenericDocument} object.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public GenericDocument build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
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 656608d..c927e34 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -31,7 +31,8 @@
 import java.util.Set;
 
 /**
- * Encapsulates a request to retrieve documents by namespace and URI.
+ * Encapsulates a request to retrieve documents by namespace and URIs from the {@link
+ * AppSearchSession} database.
  *
  * @see AppSearchSession#getByUri
  */
@@ -56,13 +57,13 @@
         mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap);
     }
 
-    /** Returns the namespace to get documents from. */
+    /** Returns the namespace attached to the request. */
     @NonNull
     public String getNamespace() {
         return mNamespace;
     }
 
-    /** Returns the URIs to get from the namespace. */
+    /** Returns the set of URIs attached to the request. */
     @NonNull
     public Set<String> getUris() {
         return Collections.unmodifiableSet(mUris);
@@ -100,7 +101,11 @@
         return mTypePropertyPathsMap;
     }
 
-    /** Builder for {@link GetByUriRequest} objects. */
+    /**
+     * Builder for {@link GetByUriRequest} objects.
+     *
+     * <p>Once {@link #build} is called, the instance can no longer be used.
+     */
     public static final class Builder {
         private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
         private final Set<String> mUris = new ArraySet<>();
@@ -108,9 +113,12 @@
         private boolean mBuilt = false;
 
         /**
-         * Sets which namespace these documents will be retrieved from.
+         * Sets the namespace to retrieve documents for.
          *
-         * <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
+         * <p>If this is not called, the namespace defaults to {@link
+         * GenericDocument#DEFAULT_NAMESPACE}.
+         *
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public Builder setNamespace(@NonNull String namespace) {
@@ -120,14 +128,22 @@
             return this;
         }
 
-        /** Adds one or more URIs to the request. */
+        /**
+         * Adds one or more URIs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public Builder addUris(@NonNull String... uris) {
             Preconditions.checkNotNull(uris);
             return addUris(Arrays.asList(uris));
         }
 
-        /** Adds one or more URIs to the request. */
+        /**
+         * Adds a collection of URIs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -149,7 +165,8 @@
          * GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to
          * all results, excepting any types that have their own, specific property paths set.
          *
-         * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
+         * @throws IllegalStateException if the builder has already been used.
+         *     <p>{@see SearchSpec.Builder#addProjection(String, String...)}
          */
         @NonNull
         public Builder addProjection(@NonNull String schemaType, @NonNull String... propertyPaths) {
@@ -170,7 +187,8 @@
          * GetByUriRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will apply to
          * all results, excepting any types that have their own, specific property paths set.
          *
-         * <p>{@see SearchSpec.Builder#addProjection(String, String...)}
+         * @throws IllegalStateException if the builder has already been used.
+         *     <p>{@see SearchSpec.Builder#addProjection(String, String...)}
          */
         @NonNull
         public Builder addProjection(
@@ -187,7 +205,11 @@
             return this;
         }
 
-        /** Builds a new {@link GetByUriRequest}. */
+        /**
+         * Builds a new {@link GetByUriRequest}.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public GetByUriRequest build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
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 198eee8..39b53b6 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -27,7 +27,8 @@
 import java.util.Set;
 
 /**
- * Encapsulates a request to remove documents by namespace and URI.
+ * Encapsulates a request to remove documents by namespace and URIs from the {@link
+ * AppSearchSession} database.
  *
  * @see AppSearchSession#remove
  */
@@ -46,22 +47,28 @@
         return mNamespace;
     }
 
-    /** Returns the URIs of documents to remove from the namespace. */
+    /** Returns the set of URIs attached to the request. */
     @NonNull
     public Set<String> getUris() {
         return Collections.unmodifiableSet(mUris);
     }
 
-    /** Builder for {@link RemoveByUriRequest} objects. */
+    /**
+     * Builder for {@link RemoveByUriRequest} objects.
+     *
+     * <p>Once {@link #build} is called, the instance can no longer be used.
+     */
     public static final class Builder {
         private String mNamespace = GenericDocument.DEFAULT_NAMESPACE;
         private final Set<String> mUris = new ArraySet<>();
         private boolean mBuilt = false;
 
         /**
-         * Sets which namespace these documents will be removed from.
+         * Sets the namespace to remove documents for.
          *
          * <p>If this is not set, it defaults to {@link GenericDocument#DEFAULT_NAMESPACE}.
+         *
+         * @throws IllegalStateException if the builder has already been used.
          */
         @NonNull
         public Builder setNamespace(@NonNull String namespace) {
@@ -71,14 +78,22 @@
             return this;
         }
 
-        /** Adds one or more URIs to the request. */
+        /**
+         * Adds one or more URIs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public Builder addUris(@NonNull String... uris) {
             Preconditions.checkNotNull(uris);
             return addUris(Arrays.asList(uris));
         }
 
-        /** Adds one or more URIs to the request. */
+        /**
+         * Adds a collection of URIs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public Builder addUris(@NonNull Collection<String> uris) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
@@ -87,7 +102,11 @@
             return this;
         }
 
-        /** Builds a new {@link RemoveByUriRequest}. */
+        /**
+         * Builds a new {@link RemoveByUriRequest}.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
         @NonNull
         public RemoveByUriRequest build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
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 4869aa3..bc99d4f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -16,10 +16,9 @@
 
 package android.app.appsearch;
 
-import static android.app.appsearch.AppSearchResult.RESULT_OK;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Bundle;
 import android.util.ArraySet;
 
 import com.android.internal.util.Preconditions;
@@ -32,23 +31,58 @@
 
 /** The response class of {@link AppSearchSession#setSchema} */
 public class SetSchemaResponse {
-    private final List<MigrationFailure> mMigrationFailures;
-    private final Set<String> mDeletedTypes;
-    private final Set<String> mMigratedTypes;
-    private final Set<String> mIncompatibleTypes;
-    private final @AppSearchResult.ResultCode int mResultCode;
 
-    SetSchemaResponse(
-            @NonNull List<MigrationFailure> migrationFailures,
-            @NonNull Set<String> deletedTypes,
-            @NonNull Set<String> migratedTypes,
-            @NonNull Set<String> incompatibleTypes,
-            @AppSearchResult.ResultCode int resultCode) {
+    private static final String DELETED_TYPES_FIELD = "deletedTypes";
+    private static final String INCOMPATIBLE_TYPES_FIELD = "incompatibleTypes";
+    private static final String MIGRATED_TYPES_FIELD = "migratedTypes";
+
+    private final Bundle mBundle;
+    /**
+     * The migrationFailures won't be saved in the bundle. Since:
+     *
+     * <ul>
+     *   <li>{@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK
+     *       side in platform. We don't need to pass it from service side via binder.
+     *   <li>Translate multiple {@link MigrationFailure}s to bundles in {@link Builder} and then
+     *       back in constructor will be a huge waste.
+     * </ul>
+     */
+    private final List<MigrationFailure> mMigrationFailures;
+
+    /** Cache of the inflated deleted schema types. Comes from inflating mBundles at first use. */
+    @Nullable private Set<String> mDeletedTypes;
+
+    /** Cache of the inflated migrated schema types. Comes from inflating mBundles at first use. */
+    @Nullable private Set<String> mMigratedTypes;
+
+    /**
+     * Cache of the inflated incompatible schema types. Comes from inflating mBundles at first use.
+     */
+    @Nullable private Set<String> mIncompatibleTypes;
+
+    SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
+        mBundle = Preconditions.checkNotNull(bundle);
         mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
-        mDeletedTypes = Preconditions.checkNotNull(deletedTypes);
-        mMigratedTypes = Preconditions.checkNotNull(migratedTypes);
-        mIncompatibleTypes = Preconditions.checkNotNull(incompatibleTypes);
-        mResultCode = resultCode;
+    }
+
+    SetSchemaResponse(@NonNull Bundle bundle) {
+        this(bundle, /*migrationFailures=*/ Collections.emptyList());
+    }
+
+    /**
+     * Returns the {@link Bundle} populated by this builder.
+     *
+     * @hide
+     */
+    @NonNull
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /** TODO(b/177266929): Remove this deprecated method */
+    //@Deprecated
+    public boolean isSuccess() {
+        return true;
     }
 
     /**
@@ -72,6 +106,12 @@
      */
     @NonNull
     public Set<String> getDeletedTypes() {
+        if (mDeletedTypes == null) {
+            mDeletedTypes =
+                    new ArraySet<>(
+                            Preconditions.checkNotNull(
+                                    mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
+        }
         return Collections.unmodifiableSet(mDeletedTypes);
     }
 
@@ -81,6 +121,12 @@
      */
     @NonNull
     public Set<String> getMigratedTypes() {
+        if (mMigratedTypes == null) {
+            mMigratedTypes =
+                    new ArraySet<>(
+                            Preconditions.checkNotNull(
+                                    mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
+        }
         return Collections.unmodifiableSet(mMigratedTypes);
     }
 
@@ -96,22 +142,28 @@
      */
     @NonNull
     public Set<String> getIncompatibleTypes() {
+        if (mIncompatibleTypes == null) {
+            mIncompatibleTypes =
+                    new ArraySet<>(
+                            Preconditions.checkNotNull(
+                                    mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
+        }
         return Collections.unmodifiableSet(mIncompatibleTypes);
     }
 
-    /** Returns {@code true} if all {@link AppSearchSchema}s are successful set to the system. */
-    public boolean isSuccess() {
-        return mResultCode == RESULT_OK;
-    }
-
-    @Override
+    /**
+     * Translates the {@link SetSchemaResponse}'s bundle to {@link Builder}.
+     *
+     * @hide
+     */
     @NonNull
-    public String toString() {
-        return "{\n  Does setSchema success? : "
-                + isSuccess()
-                + "\n  failures: "
-                + mMigrationFailures
-                + "\n}";
+    // TODO(b/179302942) change to Builder(mBundle) powered by mBundle.deepCopy
+    public Builder toBuilder() {
+        return new Builder()
+                .addDeletedTypes(getDeletedTypes())
+                .addIncompatibleTypes(getIncompatibleTypes())
+                .addMigratedTypes(getMigratedTypes())
+                .addMigrationFailures(mMigrationFailures);
     }
 
     /**
@@ -120,44 +172,26 @@
      * @hide
      */
     public static class Builder {
-        private final List<MigrationFailure> mMigrationFailures = new ArrayList<>();
-        private final Set<String> mDeletedTypes = new ArraySet<>();
-        private final Set<String> mMigratedTypes = new ArraySet<>();
-        private final Set<String> mIncompatibleTypes = new ArraySet<>();
-        private @AppSearchResult.ResultCode int mResultCode = RESULT_OK;
+        private final ArrayList<MigrationFailure> mMigrationFailures = new ArrayList<>();
+        private final ArrayList<String> mDeletedTypes = new ArrayList<>();
+        private final ArrayList<String> mMigratedTypes = new ArrayList<>();
+        private final ArrayList<String> mIncompatibleTypes = new ArrayList<>();
         private boolean mBuilt = false;
 
-        /** Adds a {@link MigrationFailure}. */
+        /** Adds {@link MigrationFailure}s to the list of migration failures. */
         @NonNull
-        public Builder setFailure(
-                @NonNull String schemaType,
-                @NonNull String namespace,
-                @NonNull String uri,
-                @NonNull AppSearchResult<Void> failureResult) {
+        public Builder addMigrationFailures(
+                @NonNull Collection<MigrationFailure> migrationFailures) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schemaType);
-            Preconditions.checkNotNull(namespace);
-            Preconditions.checkNotNull(uri);
-            Preconditions.checkNotNull(failureResult);
-            Preconditions.checkState(!failureResult.isSuccess());
-            mMigrationFailures.add(new MigrationFailure(schemaType, namespace, uri, failureResult));
+            mMigrationFailures.addAll(Preconditions.checkNotNull(migrationFailures));
             return this;
         }
 
-        /** Adds a {@link MigrationFailure}. */
+        /** Adds a {@link MigrationFailure} to the list of migration failures. */
         @NonNull
-        public Builder setFailure(
-                @NonNull String schemaType,
-                @NonNull String namespace,
-                @NonNull String uri,
-                @AppSearchResult.ResultCode int resultCode,
-                @Nullable String errorMessage) {
-            mMigrationFailures.add(
-                    new MigrationFailure(
-                            schemaType,
-                            namespace,
-                            uri,
-                            AppSearchResult.newFailedResult(resultCode, errorMessage)));
+        public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mMigrationFailures.add(Preconditions.checkNotNull(migrationFailure));
             return this;
         }
 
@@ -169,6 +203,14 @@
             return this;
         }
 
+        /** Adds one deletedType to the list of deleted schema types. */
+        @NonNull
+        public Builder addDeletedType(@NonNull String deletedType) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mDeletedTypes.add(Preconditions.checkNotNull(deletedType));
+            return this;
+        }
+
         /** Adds incompatibleTypes to the list of incompatible schema types. */
         @NonNull
         public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
@@ -177,6 +219,14 @@
             return this;
         }
 
+        /** Adds one incompatibleType to the list of incompatible schema types. */
+        @NonNull
+        public Builder addIncompatibleType(@NonNull String incompatibleType) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mIncompatibleTypes.add(Preconditions.checkNotNull(incompatibleType));
+            return this;
+        }
+
         /** Adds migratedTypes to the list of migrated schema types. */
         @NonNull
         public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
@@ -185,11 +235,11 @@
             return this;
         }
 
-        /** Sets the {@link AppSearchResult.ResultCode} of the response. */
+        /** Adds one migratedType to the list of migrated schema types. */
         @NonNull
-        public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
+        public Builder addMigratedType(@NonNull String migratedType) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mResultCode = resultCode;
+            mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
             return this;
         }
 
@@ -197,13 +247,15 @@
         @NonNull
         public SetSchemaResponse build() {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Bundle bundle = new Bundle();
+            bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
+            bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
+            bundle.putStringArrayList(MIGRATED_TYPES_FIELD, mMigratedTypes);
             mBuilt = true;
-            return new SetSchemaResponse(
-                    mMigrationFailures,
-                    mDeletedTypes,
-                    mMigratedTypes,
-                    mIncompatibleTypes,
-                    mResultCode);
+            // Avoid converting the potential thousands of MigrationFailures to Pracelable and
+            // back just for put in bundle. In platform, we should set MigrationFailures in
+            // AppSearchSession after we pass SetSchemaResponse via binder.
+            return new SetSchemaResponse(bundle, mMigrationFailures);
         }
     }
 
@@ -212,38 +264,44 @@
      * {@link AppSearchSession#setSchema}.
      */
     public static class MigrationFailure {
-        private final String mSchemaType;
-        private final String mNamespace;
-        private final String mUri;
-        AppSearchResult<Void> mFailureResult;
+        private static final String SCHEMA_TYPE_FIELD = "schemaType";
+        private static final String NAMESPACE_FIELD = "namespace";
+        private static final String URI_FIELD = "uri";
+        private static final String ERROR_MESSAGE_FIELD = "errorMessage";
+        private static final String RESULT_CODE_FIELD = "resultCode";
 
-        MigrationFailure(
-                @NonNull String schemaType,
-                @NonNull String namespace,
-                @NonNull String uri,
-                @NonNull AppSearchResult<Void> result) {
-            mSchemaType = schemaType;
-            mNamespace = namespace;
-            mUri = uri;
-            mFailureResult = result;
+        private final Bundle mBundle;
+
+        MigrationFailure(@NonNull Bundle bundle) {
+            mBundle = bundle;
+        }
+
+        /**
+         * Returns the Bundle of the {@link MigrationFailure}.
+         *
+         * @hide
+         */
+        @NonNull
+        public Bundle getBundle() {
+            return mBundle;
         }
 
         /** Returns the schema type of the {@link GenericDocument} that fails to be migrated. */
         @NonNull
         public String getSchemaType() {
-            return mSchemaType;
+            return mBundle.getString(SCHEMA_TYPE_FIELD, /*defaultValue=*/ "");
         }
 
         /** Returns the namespace of the {@link GenericDocument} that fails to be migrated. */
         @NonNull
         public String getNamespace() {
-            return mNamespace;
+            return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
         }
 
         /** Returns the uri of the {@link GenericDocument} that fails to be migrated. */
         @NonNull
         public String getUri() {
-            return mUri;
+            return mBundle.getString(URI_FIELD, /*defaultValue=*/ "");
         }
 
         /**
@@ -252,7 +310,69 @@
          */
         @NonNull
         public AppSearchResult<Void> getAppSearchResult() {
-            return mFailureResult;
+            return AppSearchResult.newFailedResult(
+                    mBundle.getInt(RESULT_CODE_FIELD),
+                    mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
+        }
+
+        /**
+         * Builder for {@link MigrationFailure} objects.
+         *
+         * @hide
+         */
+        public static class Builder {
+            private String mSchemaType;
+            private String mNamespace;
+            private String mUri;
+            private final Bundle mBundle = new Bundle();
+            private AppSearchResult<Void> mFailureResult;
+            private boolean mBuilt = false;
+
+            /** Sets the schema type for the {@link MigrationFailure}. */
+            @NonNull
+            public Builder setSchemaType(@NonNull String schemaType) {
+                Preconditions.checkState(!mBuilt, "Builder has already been used");
+                mSchemaType = Preconditions.checkNotNull(schemaType);
+                return this;
+            }
+
+            /** Sets the namespace for the {@link MigrationFailure}. */
+            @NonNull
+            public Builder setNamespace(@NonNull String namespace) {
+                Preconditions.checkState(!mBuilt, "Builder has already been used");
+                mNamespace = Preconditions.checkNotNull(namespace);
+                return this;
+            }
+
+            /** Sets the uri for the {@link MigrationFailure}. */
+            @NonNull
+            public Builder setUri(@NonNull String uri) {
+                Preconditions.checkState(!mBuilt, "Builder has already been used");
+                mUri = Preconditions.checkNotNull(uri);
+                return this;
+            }
+
+            /** Sets the failure {@link AppSearchResult} for the {@link MigrationFailure}. */
+            @NonNull
+            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);
+                return this;
+            }
+
+            /** Builds a {@link MigrationFailure} object. */
+            @NonNull
+            public MigrationFailure build() {
+                Preconditions.checkState(!mBuilt, "Builder has already been used");
+                mBundle.putString(SCHEMA_TYPE_FIELD, mSchemaType);
+                mBundle.putString(NAMESPACE_FIELD, mNamespace);
+                mBundle.putString(URI_FIELD, mUri);
+                mBundle.putString(ERROR_MESSAGE_FIELD, mFailureResult.getErrorMessage());
+                mBundle.putInt(RESULT_CODE_FIELD, mFailureResult.getResultCode());
+                mBuilt = true;
+                return new MigrationFailure(mBundle);
+            }
         }
     }
 }
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
deleted file mode 100644
index f04ace6..0000000
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResult.java
+++ /dev/null
@@ -1,125 +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.app.appsearch;
-
-import android.annotation.NonNull;
-import android.os.Bundle;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This class represents the results of setSchema().
- *
- * @hide
- */
-public class SetSchemaResult {
-
-    public static final String DELETED_SCHEMA_TYPES_FIELD = "deletedSchemaTypes";
-    public static final String INCOMPATIBLE_SCHEMA_TYPES_FIELD = "incompatibleSchemaTypes";
-    public static final String RESULT_CODE_FIELD = "resultCode";
-    private final List<String> mDeletedSchemaTypes;
-    private final List<String> mIncompatibleSchemaTypes;
-    private final Bundle mBundle;
-
-    SetSchemaResult(@NonNull Bundle bundle) {
-        mBundle = Preconditions.checkNotNull(bundle);
-        mDeletedSchemaTypes =
-                Preconditions.checkNotNull(mBundle.getStringArrayList(DELETED_SCHEMA_TYPES_FIELD));
-        mIncompatibleSchemaTypes =
-                Preconditions.checkNotNull(
-                        mBundle.getStringArrayList(INCOMPATIBLE_SCHEMA_TYPES_FIELD));
-    }
-
-    /** Returns the {@link Bundle} of this class. */
-    @NonNull
-    public Bundle getBundle() {
-        return mBundle;
-    }
-
-    /** returns all deleted schema types in this setSchema call. */
-    @NonNull
-    public List<String> getDeletedSchemaTypes() {
-        return Collections.unmodifiableList(mDeletedSchemaTypes);
-    }
-
-    /** returns all incompatible schema types in this setSchema call. */
-    @NonNull
-    public List<String> getIncompatibleSchemaTypes() {
-        return Collections.unmodifiableList(mIncompatibleSchemaTypes);
-    }
-
-    /**
-     * returns the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
-     * AppSearchSession#setSchema} call.
-     */
-    public int getResultCode() {
-        return mBundle.getInt(RESULT_CODE_FIELD);
-    }
-
-    /** Builder for {@link SetSchemaResult} objects. */
-    public static final class Builder {
-        private final ArrayList<String> mDeletedSchemaTypes = new ArrayList<>();
-        private final ArrayList<String> mIncompatibleSchemaTypes = new ArrayList<>();
-        @AppSearchResult.ResultCode private int mResultCode;
-        private boolean mBuilt = false;
-
-        /** Adds a deletedSchemaTypes to the {@link SetSchemaResult}. */
-        @NonNull
-        public Builder addDeletedSchemaType(@NonNull String deletedSchemaType) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mDeletedSchemaTypes.add(Preconditions.checkNotNull(deletedSchemaType));
-            return this;
-        }
-
-        /** Adds a incompatible SchemaTypes to the {@link SetSchemaResult}. */
-        @NonNull
-        public Builder addIncompatibleSchemaType(@NonNull String incompatibleSchemaTypes) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mIncompatibleSchemaTypes.add(Preconditions.checkNotNull(incompatibleSchemaTypes));
-            return this;
-        }
-
-        /**
-         * Sets the {@link android.app.appsearch.AppSearchResult.ResultCode} of the {@link
-         * AppSearchSession#setSchema} call to the {@link SetSchemaResult}
-         */
-        @NonNull
-        public Builder setResultCode(@AppSearchResult.ResultCode int resultCode) {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mResultCode = resultCode;
-            return this;
-        }
-
-        /** Builds a {@link SetSchemaResult}. */
-        @NonNull
-        public SetSchemaResult build() {
-            Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Bundle bundle = new Bundle();
-            bundle.putStringArrayList(
-                    SetSchemaResult.DELETED_SCHEMA_TYPES_FIELD, mDeletedSchemaTypes);
-            bundle.putStringArrayList(
-                    SetSchemaResult.INCOMPATIBLE_SCHEMA_TYPES_FIELD, mIncompatibleSchemaTypes);
-            bundle.putInt(RESULT_CODE_FIELD, mResultCode);
-            mBuilt = true;
-            return new SetSchemaResult(bundle);
-        }
-    }
-}
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 271129b..a45fa39 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -41,6 +41,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -58,8 +59,11 @@
     private PackageManagerInternal mPackageManagerInternal;
     private ImplInstanceManager mImplInstanceManager;
 
-    // Cache of unlocked user ids so we don't have to query UserManager service each time.
-    private final Set<Integer> mUnlockedUserIds = new ArraySet<>();
+    // 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
+    // the unlocked status of user ids.
+    @GuardedBy("mUnlockedUserIdsLocked")
+    private final Set<Integer> mUnlockedUserIdsLocked = new ArraySet<>();
 
     public AppSearchManagerService(Context context) {
         super(context);
@@ -74,7 +78,9 @@
 
     @Override
     public void onUserUnlocked(@NonNull TargetUser user) {
-        mUnlockedUserIds.add(user.getUserIdentifier());
+        synchronized (mUnlockedUserIdsLocked) {
+            mUnlockedUserIdsLocked.add(user.getUserIdentifier());
+        }
     }
 
     private class Stub extends IAppSearchManager.Stub {
@@ -108,7 +114,7 @@
                         schemasPackageAccessibleBundles.entrySet()) {
                     List<PackageIdentifier> packageIdentifiers =
                             new ArrayList<>(entry.getValue().size());
-                    for (int i = 0; i < packageIdentifiers.size(); i++) {
+                    for (int i = 0; i < entry.getValue().size(); i++) {
                         packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
                     }
                     schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
@@ -503,9 +509,11 @@
         }
 
         private void verifyUserUnlocked(int callingUserId) {
-            if (!mUnlockedUserIds.contains(callingUserId)) {
-                throw new IllegalStateException(
-                        "User " + callingUserId + " is locked or not running.");
+            synchronized (mUnlockedUserIdsLocked) {
+                if (!mUnlockedUserIdsLocked.contains(callingUserId)) {
+                    throw new IllegalStateException(
+                            "User " + callingUserId + " is locked or not running.");
+                }
             }
         }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 5ea2a02..82319d4 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
 
 import java.io.File;
@@ -43,7 +44,9 @@
 
     private static ImplInstanceManager sImplInstanceManager;
 
-    private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
+    @GuardedBy("mInstancesLocked")
+    private final SparseArray<AppSearchImpl> mInstancesLocked = new SparseArray<>();
+
     private final String mGlobalQuerierPackage;
 
     private ImplInstanceManager(@NonNull String globalQuerierPackage) {
@@ -81,19 +84,16 @@
      * @return An initialized {@link AppSearchImpl} for this user
      */
     @NonNull
-    public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
-            throws AppSearchException {
-        AppSearchImpl instance = mInstances.get(userId);
-        if (instance == null) {
-            synchronized (ImplInstanceManager.class) {
-                instance = mInstances.get(userId);
-                if (instance == null) {
-                    instance = createImpl(context, userId);
-                    mInstances.put(userId, instance);
-                }
+    public AppSearchImpl getAppSearchImpl(
+            @NonNull Context context, @UserIdInt int userId) throws AppSearchException {
+        synchronized (mInstancesLocked) {
+            AppSearchImpl instance = mInstancesLocked.get(userId);
+            if (instance == null) {
+                instance = createImpl(context, userId);
+                mInstancesLocked.put(userId, instance);
             }
+            return instance;
         }
-        return instance;
     }
 
     private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
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 64dc972..babcd25 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -332,10 +332,8 @@
         for (Map.Entry<String, List<PackageIdentifier>> entry :
                 schemasPackageAccessible.entrySet()) {
             for (int i = 0; i < entry.getValue().size(); i++) {
-                // TODO(b/169883602): remove the "placeholder" uri once upstream changes to relax
-                // nested document uri rules gets synced down.
                 GenericDocument packageAccessibleDocument =
-                        new GenericDocument.Builder(/*uri=*/ "placeholder", PACKAGE_ACCESSIBLE_TYPE)
+                        new GenericDocument.Builder(/*uri=*/"", PACKAGE_ACCESSIBLE_TYPE)
                                 .setNamespace(NAMESPACE)
                                 .setPropertyString(
                                         PACKAGE_NAME_PROPERTY,
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 8bff720..6c2e30e 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
@@ -25,7 +25,7 @@
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 import android.os.Bundle;
@@ -41,7 +41,7 @@
 import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.SearchResultToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.SearchSpecToProtoConverter;
-import com.android.server.appsearch.external.localstorage.converter.SetSchemaResultToProtoConverter;
+import com.android.server.appsearch.external.localstorage.converter.SetSchemaResponseToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
 
 import com.google.android.icing.IcingSearchEngine;
@@ -73,6 +73,7 @@
 import com.google.android.icing.proto.TypePropertyMask;
 import com.google.android.icing.proto.UsageReport;
 
+import java.io.Closeable;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -119,7 +120,7 @@
  * @hide
  */
 @WorkerThread
-public final class AppSearchImpl {
+public final class AppSearchImpl implements Closeable {
     private static final String TAG = "AppSearchImpl";
 
     @VisibleForTesting static final char DATABASE_DELIMITER = '/';
@@ -151,12 +152,16 @@
     private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
 
     /**
-     * The counter to check when to call {@link #checkForOptimizeLocked(boolean)}. The interval is
-     * {@link #CHECK_OPTIMIZE_INTERVAL}.
+     * The counter to check when to call {@link #checkForOptimize}. The interval is {@link
+     * #CHECK_OPTIMIZE_INTERVAL}.
      */
     @GuardedBy("mReadWriteLock")
     private int mOptimizeIntervalCountLocked = 0;
 
+    /** Whether this instance has been closed, and therefore unusable. */
+    @GuardedBy("mReadWriteLock")
+    private boolean mClosedLocked = false;
+
     /**
      * Creates and initializes an instance of {@link AppSearchImpl} which writes data to the given
      * folder.
@@ -208,7 +213,7 @@
             } catch (AppSearchException e) {
                 Log.w(TAG, "Error initializing, resetting IcingSearchEngine.", e);
                 // Some error. Reset and see if it fixes it.
-                reset();
+                resetLocked();
                 return;
             }
 
@@ -222,11 +227,6 @@
             for (String prefixedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
                 addToMap(mNamespaceMapLocked, getPrefix(prefixedNamespace), prefixedNamespace);
             }
-
-            // TODO(b/155939114): It's possible to optimize after init, which would reduce the time
-            //   to when we're able to serve queries. Consider moving this optimize call out.
-            checkForOptimizeLocked(/* force= */ true);
-
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -240,12 +240,45 @@
     void initializeVisibilityStore() throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
+            throwIfClosedLocked();
+
             mVisibilityStoreLocked.initialize();
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
     }
 
+    @GuardedBy("mReadWriteLock")
+    private void throwIfClosedLocked() {
+        if (mClosedLocked) {
+            throw new IllegalStateException("Trying to use a closed AppSearchImpl instance.");
+        }
+    }
+
+    /**
+     * Persists data to disk and closes the instance.
+     *
+     * <p>This instance is no longer usable after it's been closed. Call {@link #create} to create a
+     * new, usable instance.
+     */
+    @Override
+    public void close() {
+        mReadWriteLock.writeLock().lock();
+        try {
+            if (mClosedLocked) {
+                return;
+            }
+
+            persistToDisk();
+            mIcingSearchEngineLocked.close();
+            mClosedLocked = true;
+        } catch (AppSearchException e) {
+            Log.w(TAG, "Error when closing AppSearchImpl.", e);
+        } finally {
+            mReadWriteLock.writeLock().unlock();
+        }
+    }
+
     /**
      * Updates the AppSearch schema for this app.
      *
@@ -259,10 +292,14 @@
      * @param schemasPackageAccessible Schema types that are visible to the specified packages.
      * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
      *     which do not comply with the new schema will be deleted.
-     * @throws AppSearchException on IcingSearchEngine error.
+     * @throws AppSearchException On IcingSearchEngine error. If the status code is
+     *     FAILED_PRECONDITION for the incompatible change, the exception will be converted to the
+     *     SetSchemaResponse.
+     * @return The response contains deleted schema types and incompatible schema types of this
+     *     call.
      */
     @NonNull
-    public SetSchemaResult setSchema(
+    public SetSchemaResponse setSchema(
             @NonNull String packageName,
             @NonNull String databaseName,
             @NonNull List<AppSearchSchema> schemas,
@@ -272,6 +309,8 @@
             throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
+            throwIfClosedLocked();
+
             SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
 
             SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
@@ -297,9 +336,16 @@
             try {
                 checkSuccess(setSchemaResultProto.getStatus());
             } catch (AppSearchException e) {
-                if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
-                        || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0) {
-                    return SetSchemaResultToProtoConverter.toSetSchemaResult(
+                // Swallow the exception for the incompatible change case. We will propagate
+                // those deleted schemas and incompatible types to the SetSchemaResponse.
+                boolean isFailedPrecondition =
+                        setSchemaResultProto.getStatus().getCode()
+                                == StatusProto.Code.FAILED_PRECONDITION;
+                boolean isIncompatible =
+                        setSchemaResultProto.getDeletedSchemaTypesCount() > 0
+                                || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0;
+                if (isFailedPrecondition && isIncompatible) {
+                    return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
                             setSchemaResultProto, prefix);
                 } else {
                     throw e;
@@ -317,7 +363,7 @@
             }
 
             Map<String, List<PackageIdentifier>> prefixedSchemasPackageAccessible =
-                    new ArrayMap<>(schemasNotPlatformSurfaceable.size());
+                    new ArrayMap<>(schemasPackageAccessible.size());
             for (Map.Entry<String, List<PackageIdentifier>> entry :
                     schemasPackageAccessible.entrySet()) {
                 prefixedSchemasPackageAccessible.put(prefix + entry.getKey(), entry.getValue());
@@ -328,16 +374,8 @@
                     prefixedSchemasNotPlatformSurfaceable,
                     prefixedSchemasPackageAccessible);
 
-            // Determine whether to schedule an immediate optimize.
-            if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
-                    || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
-                            && forceOverride)) {
-                // Any existing schemas which is not in 'schemas' will be deleted, and all
-                // documents of these types were also deleted. And so well if we force override
-                // incompatible schemas.
-                checkForOptimizeLocked(/* force= */ true);
-            }
-            return SetSchemaResultToProtoConverter.toSetSchemaResult(setSchemaResultProto, prefix);
+            return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
+                    setSchemaResultProto, prefix);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
@@ -355,44 +393,47 @@
     @NonNull
     public List<AppSearchSchema> getSchema(
             @NonNull String packageName, @NonNull String databaseName) throws AppSearchException {
-        SchemaProto fullSchema;
         mReadWriteLock.readLock().lock();
         try {
-            fullSchema = getSchemaProtoLocked();
+            throwIfClosedLocked();
+
+            SchemaProto fullSchema = getSchemaProtoLocked();
+
+            String prefix = createPrefix(packageName, databaseName);
+            List<AppSearchSchema> result = new ArrayList<>();
+            for (int i = 0; i < fullSchema.getTypesCount(); i++) {
+                String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
+                if (!prefix.equals(typePrefix)) {
+                    continue;
+                }
+                // Rewrite SchemaProto.types.schema_type
+                SchemaTypeConfigProto.Builder typeConfigBuilder =
+                        fullSchema.getTypes(i).toBuilder();
+                String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
+                typeConfigBuilder.setSchemaType(newSchemaType);
+
+                // Rewrite SchemaProto.types.properties.schema_type
+                for (int propertyIdx = 0;
+                        propertyIdx < typeConfigBuilder.getPropertiesCount();
+                        propertyIdx++) {
+                    PropertyConfigProto.Builder propertyConfigBuilder =
+                            typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+                    if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+                        String newPropertySchemaType =
+                                propertyConfigBuilder.getSchemaType().substring(prefix.length());
+                        propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+                        typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+                    }
+                }
+
+                AppSearchSchema schema =
+                        SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
+                result.add(schema);
+            }
+            return result;
         } finally {
             mReadWriteLock.readLock().unlock();
         }
-
-        String prefix = createPrefix(packageName, databaseName);
-        List<AppSearchSchema> result = new ArrayList<>();
-        for (int i = 0; i < fullSchema.getTypesCount(); i++) {
-            String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType());
-            if (!prefix.equals(typePrefix)) {
-                continue;
-            }
-            // Rewrite SchemaProto.types.schema_type
-            SchemaTypeConfigProto.Builder typeConfigBuilder = fullSchema.getTypes(i).toBuilder();
-            String newSchemaType = typeConfigBuilder.getSchemaType().substring(prefix.length());
-            typeConfigBuilder.setSchemaType(newSchemaType);
-
-            // Rewrite SchemaProto.types.properties.schema_type
-            for (int propertyIdx = 0;
-                    propertyIdx < typeConfigBuilder.getPropertiesCount();
-                    propertyIdx++) {
-                PropertyConfigProto.Builder propertyConfigBuilder =
-                        typeConfigBuilder.getProperties(propertyIdx).toBuilder();
-                if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
-                    String newPropertySchemaType =
-                            propertyConfigBuilder.getSchemaType().substring(prefix.length());
-                    propertyConfigBuilder.setSchemaType(newPropertySchemaType);
-                    typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
-                }
-            }
-
-            AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
-            result.add(schema);
-        }
-        return result;
     }
 
     /**
@@ -410,23 +451,22 @@
             @NonNull String databaseName,
             @NonNull GenericDocument document)
             throws AppSearchException {
-        DocumentProto.Builder documentBuilder =
-                GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
-        String prefix = createPrefix(packageName, databaseName);
-        addPrefixToDocument(documentBuilder, prefix);
-
-        PutResultProto putResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
+            throwIfClosedLocked();
+
+            DocumentProto.Builder documentBuilder =
+                    GenericDocumentToProtoConverter.toDocumentProto(document).toBuilder();
+            String prefix = createPrefix(packageName, databaseName);
+            addPrefixToDocument(documentBuilder, prefix);
+
+            PutResultProto putResultProto = mIcingSearchEngineLocked.put(documentBuilder.build());
             addToMap(mNamespaceMapLocked, prefix, documentBuilder.getNamespace());
-            // The existing documents with same URI will be deleted, so there maybe some resources
-            // could be released after optimize().
-            checkForOptimizeLocked(/* force= */ false);
+
+            checkSuccess(putResultProto.getStatus());
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
-        checkSuccess(putResultProto.getStatus());
     }
 
     /**
@@ -451,40 +491,42 @@
             @NonNull String uri,
             @NonNull Map<String, List<String>> typePropertyPaths)
             throws AppSearchException {
-        GetResultProto getResultProto;
-        List<TypePropertyMask> nonPrefixedPropertyMasks =
-                TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
-        List<TypePropertyMask> prefixedPropertyMasks =
-                new ArrayList<>(nonPrefixedPropertyMasks.size());
-        for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
-            TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
-            String nonPrefixedType = typePropertyMask.getSchemaType();
-            String prefixedType =
-                    nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
-                            ? nonPrefixedType
-                            : createPrefix(packageName, databaseName) + nonPrefixedType;
-            prefixedPropertyMasks.add(
-                    typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
-        }
-        GetResultSpecProto getResultSpec =
-                GetResultSpecProto.newBuilder()
-                        .addAllTypePropertyMasks(prefixedPropertyMasks)
-                        .build();
         mReadWriteLock.readLock().lock();
         try {
-            getResultProto =
+            throwIfClosedLocked();
+
+            List<TypePropertyMask> nonPrefixedPropertyMasks =
+                    TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
+            List<TypePropertyMask> prefixedPropertyMasks =
+                    new ArrayList<>(nonPrefixedPropertyMasks.size());
+            for (int i = 0; i < nonPrefixedPropertyMasks.size(); ++i) {
+                TypePropertyMask typePropertyMask = nonPrefixedPropertyMasks.get(i);
+                String nonPrefixedType = typePropertyMask.getSchemaType();
+                String prefixedType =
+                        nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
+                                ? nonPrefixedType
+                                : createPrefix(packageName, databaseName) + nonPrefixedType;
+                prefixedPropertyMasks.add(
+                        typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
+            }
+            GetResultSpecProto getResultSpec =
+                    GetResultSpecProto.newBuilder()
+                            .addAllTypePropertyMasks(prefixedPropertyMasks)
+                            .build();
+
+            GetResultProto getResultProto =
                     mIcingSearchEngineLocked.get(
                             createPrefix(packageName, databaseName) + namespace,
                             uri,
                             getResultSpec);
+            checkSuccess(getResultProto.getStatus());
+
+            DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
+            removePrefixesFromDocument(documentBuilder);
+            return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
         } finally {
             mReadWriteLock.readLock().unlock();
         }
-        checkSuccess(getResultProto.getStatus());
-
-        DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
-        removePrefixesFromDocument(documentBuilder);
-        return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
     }
 
     /**
@@ -507,17 +549,19 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
-        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
-            // Client wanted to query over some packages that weren't its own. This isn't
-            // allowed through local query so we can return early with no results.
-            return new SearchResultPage(Bundle.EMPTY);
-        }
-
         mReadWriteLock.readLock().lock();
         try {
+            throwIfClosedLocked();
+
+            List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+            if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
+                // Client wanted to query over some packages that weren't its own. This isn't
+                // allowed through local query so we can return early with no results.
+                return new SearchResultPage(Bundle.EMPTY);
+            }
+
             String prefix = createPrefix(packageName, databaseName);
-            Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
+            Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
 
             return doQueryLocked(
                     Collections.singleton(createPrefix(packageName, databaseName)),
@@ -552,6 +596,8 @@
             throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
+            throwIfClosedLocked();
+
             Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
             Set<String> prefixFilters = new ArraySet<>();
             Set<String> allPrefixes = mNamespaceMapLocked.keySet();
@@ -654,6 +700,8 @@
     public SearchResultPage getNextPage(long nextPageToken) throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
+            throwIfClosedLocked();
+
             SearchResultProto searchResultProto =
                     mIcingSearchEngineLocked.getNextPage(nextPageToken);
             checkSuccess(searchResultProto.getStatus());
@@ -674,6 +722,8 @@
     public void invalidateNextPageToken(long nextPageToken) {
         mReadWriteLock.readLock().lock();
         try {
+            throwIfClosedLocked();
+
             mIcingSearchEngineLocked.invalidateNextPageToken(nextPageToken);
         } finally {
             mReadWriteLock.readLock().unlock();
@@ -688,16 +738,19 @@
             @NonNull String uri,
             long usageTimestampMillis)
             throws AppSearchException {
-        String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
-        UsageReport report =
-                UsageReport.newBuilder()
-                        .setDocumentNamespace(prefixedNamespace)
-                        .setDocumentUri(uri)
-                        .setUsageTimestampMs(usageTimestampMillis)
-                        .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
-                        .build();
         mReadWriteLock.writeLock().lock();
         try {
+            throwIfClosedLocked();
+
+            String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
+            UsageReport report =
+                    UsageReport.newBuilder()
+                            .setDocumentNamespace(prefixedNamespace)
+                            .setDocumentUri(uri)
+                            .setUsageTimestampMs(usageTimestampMillis)
+                            .setUsageType(UsageReport.UsageType.USAGE_TYPE1)
+                            .build();
+
             ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report);
             checkSuccess(result.getStatus());
         } finally {
@@ -722,16 +775,18 @@
             @NonNull String namespace,
             @NonNull String uri)
             throws AppSearchException {
-        String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
-        DeleteResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
-            deleteResultProto = mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
-            checkForOptimizeLocked(/* force= */ false);
+            throwIfClosedLocked();
+
+            String prefixedNamespace = createPrefix(packageName, databaseName) + namespace;
+            DeleteResultProto deleteResultProto =
+                    mIcingSearchEngineLocked.delete(prefixedNamespace, uri);
+
+            checkSuccess(deleteResultProto.getStatus());
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
-        checkSuccess(deleteResultProto.getStatus());
     }
 
     /**
@@ -751,22 +806,25 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
-        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
-            // We're only removing documents within the parameter `packageName`. If we're not
-            // restricting our remove-query to this package name, then there's nothing for us to
-            // remove.
-            return;
-        }
-
-        SearchSpecProto searchSpecProto = SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
-        SearchSpecProto.Builder searchSpecBuilder =
-                searchSpecProto.toBuilder().setQuery(queryExpression);
-        DeleteByQueryResultProto deleteResultProto;
         mReadWriteLock.writeLock().lock();
         try {
+            throwIfClosedLocked();
+
+            List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+            if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
+                // We're only removing documents within the parameter `packageName`. If we're not
+                // restricting our remove-query to this package name, then there's nothing for us to
+                // remove.
+                return;
+            }
+
+            SearchSpecProto searchSpecProto =
+                    SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+            SearchSpecProto.Builder searchSpecBuilder =
+                    searchSpecProto.toBuilder().setQuery(queryExpression);
+
             String prefix = createPrefix(packageName, databaseName);
-            Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemas(prefix, searchSpec);
+            Set<String> allowedPrefixedSchemas = getAllowedPrefixSchemasLocked(prefix, searchSpec);
 
             // rewriteSearchSpecForPrefixesLocked will return false if there is nothing to search
             // over given their search filters, so we can return early and skip sending request
@@ -775,15 +833,16 @@
                     searchSpecBuilder, Collections.singleton(prefix), allowedPrefixedSchemas)) {
                 return;
             }
-            deleteResultProto = mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
-            checkForOptimizeLocked(/* force= */ true);
+            DeleteByQueryResultProto deleteResultProto =
+                    mIcingSearchEngineLocked.deleteByQuery(searchSpecBuilder.build());
+
+            // It seems that the caller wants to get success if the data matching the query is
+            // not in the DB because it was not there or was successfully deleted.
+            checkCodeOneOf(
+                    deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
         } finally {
             mReadWriteLock.writeLock().unlock();
         }
-        // It seems that the caller wants to get success if the data matching the query is not in
-        // the DB because it was not there or was successfully deleted.
-        checkCodeOneOf(
-                deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
     }
 
     /**
@@ -799,9 +858,16 @@
      * @throws AppSearchException on any error that AppSearch persist data to disk.
      */
     public void persistToDisk() throws AppSearchException {
-        PersistToDiskResultProto persistToDiskResultProto =
-                mIcingSearchEngineLocked.persistToDisk();
-        checkSuccess(persistToDiskResultProto.getStatus());
+        mReadWriteLock.writeLock().lock();
+        try {
+            throwIfClosedLocked();
+
+            PersistToDiskResultProto persistToDiskResultProto =
+                    mIcingSearchEngineLocked.persistToDisk();
+            checkSuccess(persistToDiskResultProto.getStatus());
+        } finally {
+            mReadWriteLock.writeLock().unlock();
+        }
     }
 
     /**
@@ -814,21 +880,16 @@
      *
      * @throws AppSearchException on IcingSearchEngine error.
      */
-    private void reset() throws AppSearchException {
-        ResetResultProto resetResultProto;
-        mReadWriteLock.writeLock().lock();
-        try {
-            resetResultProto = mIcingSearchEngineLocked.reset();
-            mOptimizeIntervalCountLocked = 0;
-            mSchemaMapLocked.clear();
-            mNamespaceMapLocked.clear();
+    @GuardedBy("mReadWriteLock")
+    private void resetLocked() throws AppSearchException {
+        ResetResultProto resetResultProto = mIcingSearchEngineLocked.reset();
+        mOptimizeIntervalCountLocked = 0;
+        mSchemaMapLocked.clear();
+        mNamespaceMapLocked.clear();
 
-            // Must be called after everything else since VisibilityStore may repopulate
-            // IcingSearchEngine with an initial schema.
-            mVisibilityStoreLocked.handleReset();
-        } finally {
-            mReadWriteLock.writeLock().unlock();
-        }
+        // Must be called after everything else since VisibilityStore may repopulate
+        // IcingSearchEngine with an initial schema.
+        mVisibilityStoreLocked.handleReset();
         checkSuccess(resetResultProto.getStatus());
     }
 
@@ -1079,7 +1140,8 @@
      * <p>This only checks intersection of schema filters on the search spec with those that the
      * prefix owns itself. This does not check global query permissions.
      */
-    private Set<String> getAllowedPrefixSchemas(
+    @GuardedBy("mReadWriteLock")
+    private Set<String> getAllowedPrefixSchemasLocked(
             @NonNull String prefix, @NonNull SearchSpec searchSpec) {
         Set<String> allowedPrefixedSchemas = new ArraySet<>();
 
@@ -1295,35 +1357,72 @@
     /**
      * Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
      *
-     * <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
-     * safety.
+     * <p>This method should be only called after a mutation to local storage backend which deletes
+     * a mass of data and could release lots resources after {@link IcingSearchEngine#optimize()}.
+     *
+     * <p>This method will trigger {@link IcingSearchEngine#getOptimizeInfo()} to check resources
+     * that could be released for every {@link #CHECK_OPTIMIZE_INTERVAL} mutations.
      *
      * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
      * GetOptimizeInfoResultProto} shows there is enough resources could be released.
      *
-     * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per {@link
-     * #CHECK_OPTIMIZE_INTERVAL} of remove executions.
-     *
-     * @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}.
+     * @param mutationSize The number of how many mutations have been executed for current request.
+     *     An inside counter will accumulates it. Once the counter reaches {@link
+     *     #CHECK_OPTIMIZE_INTERVAL}, {@link IcingSearchEngine#getOptimizeInfo()} will be triggered
+     *     and the counter will be reset.
      */
-    @GuardedBy("mReadWriteLock")
-    private void checkForOptimizeLocked(boolean force) throws AppSearchException {
-        ++mOptimizeIntervalCountLocked;
-        if (force || mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
-            mOptimizeIntervalCountLocked = 0;
+    public void checkForOptimize(int mutationSize) throws AppSearchException {
+        mReadWriteLock.writeLock().lock();
+        try {
+            mOptimizeIntervalCountLocked += mutationSize;
+            if (mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
+                checkForOptimize();
+            }
+        } finally {
+            mReadWriteLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
+     *
+     * <p>This method will directly trigger {@link IcingSearchEngine#getOptimizeInfo()} to check
+     * resources that could be released.
+     *
+     * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
+     * GetOptimizeInfoResultProto} shows there is enough resources could be released.
+     */
+    public void checkForOptimize() throws AppSearchException {
+        mReadWriteLock.writeLock().lock();
+        try {
             GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
             checkSuccess(optimizeInfo.getStatus());
+            mOptimizeIntervalCountLocked = 0;
             // Second threshold, decide when to call optimize().
             if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
                     || optimizeInfo.getEstimatedOptimizableBytes() >= OPTIMIZE_THRESHOLD_BYTES) {
-                // TODO(b/155939114): call optimize in the same thread will slow down api calls
-                //  significantly. Move this call to background.
-                OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
-                checkSuccess(optimizeResultProto.getStatus());
+                optimize();
             }
-            // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
-            //  a field to indicate lost_schema and lost_documents in OptimizeResultProto.
-            //  go/icing-library-apis.
+        } finally {
+            mReadWriteLock.writeLock().unlock();
+        }
+        // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
+        //  a field to indicate lost_schema and lost_documents in OptimizeResultProto.
+        //  go/icing-library-apis.
+    }
+
+    /**
+     * Triggers {@link IcingSearchEngine#optimize()} directly.
+     *
+     * <p>This method should be only called as a scheduled task in AppSearch Platform backend.
+     */
+    public void optimize() throws AppSearchException {
+        mReadWriteLock.writeLock().lock();
+        try {
+            OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
+            checkSuccess(optimizeResultProto.getStatus());
+        } finally {
+            mReadWriteLock.writeLock().unlock();
         }
     }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
index b3f8264..a501e99 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
@@ -76,7 +76,7 @@
         int currentVersion = mCurrentVersionMap.get(schemaType);
         int finalVersion = mFinalVersionMap.get(schemaType);
         try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
-            // TODO(b/151178558) change the output stream so that we can use it in platform
+            // TODO(b/177266929) change the output stream so that we can use it in platform
             CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
             SearchResultPage searchResultPage =
                     mAppSearchImpl.query(
@@ -126,11 +126,13 @@
                 try {
                     mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
                 } catch (Throwable t) {
-                    responseBuilder.setFailure(
-                            document.getSchemaType(),
-                            document.getNamespace(),
-                            document.getUri(),
-                            throwableToFailedResult(t));
+                    responseBuilder.addMigrationFailure(
+                            new SetSchemaResponse.MigrationFailure.Builder()
+                                    .setNamespace(document.getNamespace())
+                                    .setSchemaType(document.getSchemaType())
+                                    .setUri(document.getUri())
+                                    .setAppSearchResult(throwableToFailedResult(t))
+                                    .build());
                 }
             }
             mAppSearchImpl.persistToDisk();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
similarity index 70%
rename from apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
rename to apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
index e1e7d46..a0f39ec 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
@@ -17,45 +17,41 @@
 package com.android.server.appsearch.external.localstorage.converter;
 
 import android.annotation.NonNull;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
 
 import com.android.internal.util.Preconditions;
 
 import com.google.android.icing.proto.SetSchemaResultProto;
 
 /**
- * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
+ * Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
  *
  * @hide
  */
-public class SetSchemaResultToProtoConverter {
+public class SetSchemaResponseToProtoConverter {
 
-    private SetSchemaResultToProtoConverter() {}
+    private SetSchemaResponseToProtoConverter() {}
 
     /**
-     * Translate a {@link SetSchemaResultProto} into {@link SetSchemaResult}.
+     * Translate a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
      *
      * @param proto The {@link SetSchemaResultProto} containing results.
      * @param prefix The prefix need to removed from schemaTypes
-     * @return {@link SetSchemaResult} of results.
+     * @return The {@link SetSchemaResponse} object.
      */
     @NonNull
-    public static SetSchemaResult toSetSchemaResult(
+    public static SetSchemaResponse toSetSchemaResponse(
             @NonNull SetSchemaResultProto proto, @NonNull String prefix) {
         Preconditions.checkNotNull(proto);
         Preconditions.checkNotNull(prefix);
-        SetSchemaResult.Builder builder =
-                new SetSchemaResult.Builder()
-                        .setResultCode(
-                                ResultCodeToProtoConverter.toResultCode(
-                                        proto.getStatus().getCode()));
+        SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
 
         for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
-            builder.addDeletedSchemaType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
+            builder.addDeletedType(proto.getDeletedSchemaTypes(i).substring(prefix.length()));
         }
 
         for (int i = 0; i < proto.getIncompatibleSchemaTypesCount(); i++) {
-            builder.addIncompatibleSchemaType(
+            builder.addIncompatibleType(
                     proto.getIncompatibleSchemaTypes(i).substring(prefix.length()));
         }
 
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 6ba5572..d076db3 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I2bf8bd9db1b71b7da4ab50dd7480e4529678413a
+Ia9a8daef1a6d7d9432f7808d440abd64f4797701
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 eb1623e..6595d8d 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
@@ -37,8 +37,9 @@
 import java.util.concurrent.Executors;
 
 /**
- * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via
- * a consistent interface.
+ * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
+ * consistent interface.
+ *
  * @hide
  */
 public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@@ -47,7 +48,13 @@
 
     @NonNull
     public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
-        Context context = ApplicationProvider.getApplicationContext();
+        return createGlobalSearchSession(ApplicationProvider.getApplicationContext());
+    }
+
+    /** Only for use when called from a non-instrumented context. */
+    @NonNull
+    public static ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession(
+            @NonNull Context context) {
         AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
         SettableFuture<AppSearchResult<GlobalSearchSession>> future = SettableFuture.create();
         ExecutorService executor = Executors.newCachedThreadPool();
@@ -62,7 +69,6 @@
             @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
         mGlobalSearchSession = Preconditions.checkNotNull(session);
         mExecutor = Preconditions.checkNotNull(executor);
-
     }
 
     @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 8e62c0e..ea21e19 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
@@ -77,20 +77,24 @@
      *       android.app.appsearch.exceptions.AppSearchException} with the {@link
      *       AppSearchResult#RESULT_INVALID_SCHEMA} error code, all documents which are not
      *       compatible with the new schema will be deleted and the incompatible schema will be
-     *       applied.
+     *       applied. Incompatible types and deleted types will be set into {@link
+     *       SetSchemaResponse#getIncompatibleTypes()} and {@link
+     *       SetSchemaResponse#getDeletedTypes()}, respectively.
      *   <li>Add a {@link android.app.appsearch.AppSearchSchema.Migrator} for each incompatible type
      *       and make no deletion. The migrator will migrate documents from it's old schema version
-     *       to the new version. See the migration section below.
+     *       to the new version. Migrated types will be set into both {@link
+     *       SetSchemaResponse#getIncompatibleTypes()} and {@link
+     *       SetSchemaResponse#getMigratedTypes()}. See the migration section below.
      * </ul>
      *
      * <p>It is a no-op to set the same schema as has been previously set; this is handled
      * efficiently.
      *
-     * <p>By default, documents are visible on platform surfaces. To opt out, call {@code
-     * SetSchemaRequest.Builder#setPlatformSurfaceable} with {@code surfaceable} as false. Any
-     * visibility settings apply only to the schemas that are included in the {@code request}.
-     * Visibility settings for a schema type do not apply or persist across {@link
-     * SetSchemaRequest}s.
+     * <p>By default, documents are visible on platform surfaces. To opt out, call
+     * {@link SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi} with {@code visible} as
+     * false. Any visibility settings apply only to the schemas that are included in the
+     * {@code request}. Visibility settings for a schema type do not persist across
+     * {@link #setSchema} calls.
      *
      * <p>Migration: make non-backwards-compatible changes will delete all stored documents in old
      * schema. You can save your documents by setting {@link
@@ -109,15 +113,11 @@
      * backwards-compatible, and stored documents won't have any observable changes.
      *
      * @param request The schema update request.
-     * @return The pending {@link SetSchemaResponse} of performing this operation. Success if the
-     *     the schema has been set and any migrations has been done. Otherwise, the failure {@link
-     *     android.app.appsearch.SetSchemaResponse.MigrationFailure} indicates which document is
-     *     fail to be migrated.
+     * @return A {@link ListenableFuture} with exception if we hit any error. Or the pending {@link
+     *     SetSchemaResponse} of performing this operation, if the schema has been successfully set.
      * @see android.app.appsearch.AppSearchSchema.Migrator
      * @see android.app.appsearch.AppSearchMigrationHelper.Transformer
      */
-    // TODO(b/169883602): Change @code references to @link when setPlatformSurfaceable APIs are
-    //  exposed.
     @NonNull
     ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request);
 
@@ -146,57 +146,79 @@
     ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
 
     /**
-     * Retrieves {@link GenericDocument}s by URI.
+     * Gets {@link GenericDocument} objects by URIs and namespace from the {@link
+     * AppSearchSessionShim} database.
      *
-     * @param request {@link GetByUriRequest} containing URIs to be retrieved.
-     * @return The pending result of performing this operation. The keys of the returned {@link
-     *     AppSearchBatchResult} are the input URIs. The values are the returned {@link
-     *     GenericDocument}s on success, or a failed {@link AppSearchResult} otherwise. URIs that
-     *     are not found will return a failed {@link AppSearchResult} with a result code of {@link
-     *     AppSearchResult#RESULT_NOT_FOUND}.
+     * @param request a request containing URIs and namespace to get documents for.
+     * @return A {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
+     *     keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
+     *     GetByUriRequest} object. The values are either the corresponding {@link GenericDocument}
+     *     object for the URI on success, or an {@link AppSearchResult} object on failure. For
+     *     example, if a URI is not found, the value for that URI will be set to an {@link
+     *     AppSearchResult} object with result code: {@link AppSearchResult#RESULT_NOT_FOUND}.
      */
     @NonNull
     ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByUri(
             @NonNull GetByUriRequest request);
 
     /**
-     * Searches for documents based on a given query string.
+     * Retrieves documents from the open {@link AppSearchSessionShim} that match a given query
+     * string and type of search provided.
      *
-     * <p>Currently we support following features in the raw query format:
+     * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
+     * and operators.
+     *
+     * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
+     * returned.
+     *
+     * <p>For query strings with a single term and no operators, documents that match the provided
+     * query string and {@link SearchSpec} will be returned.
+     *
+     * <p>The following operators are supported:
      *
      * <ul>
-     *   <li>AND
-     *       <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
-     *       Example: hello world matches documents that have both ‘hello’ and ‘world’
+     *   <li>AND (implicit)
+     *       <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
+     *       <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
+     *       including "AND" in a query string will treat "AND" as a term, returning documents that
+     *       also contain "AND".
+     *       <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
+     *       "banana".
+     *       <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
+     *       <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
+     *       "cherry".
      *   <li>OR
-     *       <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
-     *       dog OR puppy
-     *   <li>Exclusion
-     *       <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
-     *       -dog excludes the term ‘dog’
-     *   <li>Grouping terms
-     *       <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
-     *       “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
-     *       Example: (dog puppy) (cat kitten) two one group containing two terms.
-     *   <li>Property restricts
-     *       <p>Specifies which properties of a document to specifically match terms in (e.g. “match
-     *       documents where the ‘subject’ property contains ‘important’”). Example:
-     *       subject:important matches documents with the term ‘important’ in the ‘subject’ property
-     *   <li>Schema type restricts
-     *       <p>This is similar to property restricts, but allows for restricts on top-level
-     *       document fields, such as schema_type. Clients should be able to limit their query to
-     *       documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
-     *       schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
-     *       match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
-     *       type or the ‘Video’ schema type.
+     *       <p>OR is an operator that matches documents that contain <i>any</i> provided term.
+     *       <p>Example: "apple OR banana" matches documents that contain either "apple" or
+     *       "banana".
+     *       <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
+     *       "banana", or "cherry".
+     *   <li>Exclusion (-)
+     *       <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
+     *       provided term.
+     *       <p>Example: "-apple" matches documents that do not contain "apple".
+     *   <li>Grouped Terms
+     *       <p>For queries that require multiple operators and terms, terms can be grouped into
+     *       subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
+     *       <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
+     *       "donut" or "bagel" and either "coffee" or "tea".
+     *   <li>Property Restricts
+     *       <p>For queries that require a term to match a specific {@link AppSearchSchema} property
+     *       of a document, a ":" must be included between the property name and the term.
+     *       <p>Example: "subject:important" matches documents that contain the term "important" in
+     *       the "subject" property.
      * </ul>
      *
+     * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
+     * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
+     *
      * <p>This method is lightweight. The heavy work will be done in {@link
      * SearchResultsShim#getNextPage()}.
      *
-     * @param queryExpression Query String to search.
-     * @param searchSpec Spec for setting filters, raw query etc.
-     * @return The search result of performing this operation.
+     * @param queryExpression query string to search.
+     * @param searchSpec spec for setting document filters, adding projection, setting term match
+     *     type, etc.
+     * @return a {@link SearchResultsShim} object for retrieved matched documents.
      */
     @NonNull
     SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
@@ -221,13 +243,22 @@
     ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request);
 
     /**
-     * Removes {@link GenericDocument}s from the index by URI.
+     * Removes {@link GenericDocument} objects by URIs and namespace from the {@link
+     * AppSearchSessionShim} database.
      *
-     * @param request Request containing URIs to be removed.
-     * @return The pending result of performing this operation. The keys of the returned {@link
-     *     AppSearchBatchResult} are the input URIs. The values are {@code null} on success, or a
-     *     failed {@link AppSearchResult} otherwise. URIs that are not found will return a failed
-     *     {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
+     * <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.
+     *
+     * @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
+     * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
+     *     keys of the {@link AppSearchBatchResult} represent the input URIs from the {@link
+     *     RemoveByUriRequest} object. The values are either {@code null} on success, or a failed
+     *     {@link AppSearchResult} otherwise. URIs that are not found will return a failed {@link
+     *     AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
      */
     @NonNull
     ListenableFuture<AppSearchBatchResult<String, Void>> remove(
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index 44d5180..37717d6 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -25,7 +25,6 @@
 import android.app.appsearch.GetByUriRequest;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultsShim;
-import android.app.appsearch.SetSchemaResponse;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -43,15 +42,6 @@
         return result;
     }
 
-    // TODO(b/151178558) check setSchemaResponse.xxxtypes for the test need to verify.
-    public static void checkIsSetSchemaResponseSuccess(Future<SetSchemaResponse> future)
-            throws Exception {
-        SetSchemaResponse setSchemaResponse = future.get();
-        assertWithMessage("SetSchemaResponse not successful.")
-                .that(setSchemaResponse.isSuccess())
-                .isTrue();
-    }
-
     public static List<GenericDocument> doGet(
             AppSearchSessionShim session, String namespace, String... uris) throws Exception {
         AppSearchBatchResult<String, GenericDocument> result =
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index b96f99e..31c934f 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -27,43 +27,26 @@
  */
 public interface GlobalSearchSessionShim extends Closeable {
     /**
-     * Searches across all documents in the storage based on a given query string.
+     * Retrieves documents from all AppSearch databases that the querying application has access to.
      *
-     * <p>Currently we support following features in the raw query format:
+     * <p>Applications can be granted access to documents by specifying {@link
+     * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}, or {@link
+     * SetSchemaRequest.Builder#setDocumentClassVisibilityForPackage} when building a schema.
      *
-     * <ul>
-     *   <li>AND
-     *       <p>AND joins (e.g. “match documents that have both the terms ‘dog’ and ‘cat’”).
-     *       Example: hello world matches documents that have both ‘hello’ and ‘world’
-     *   <li>OR
-     *       <p>OR joins (e.g. “match documents that have either the term ‘dog’ or ‘cat’”). Example:
-     *       dog OR puppy
-     *   <li>Exclusion
-     *       <p>Exclude a term (e.g. “match documents that do not have the term ‘dog’”). Example:
-     *       -dog excludes the term ‘dog’
-     *   <li>Grouping terms
-     *       <p>Allow for conceptual grouping of subqueries to enable hierarchical structures (e.g.
-     *       “match documents that have either ‘dog’ or ‘puppy’, and either ‘cat’ or ‘kitten’”).
-     *       Example: (dog puppy) (cat kitten) two one group containing two terms.
-     *   <li>Property restricts
-     *       <p>Specifies which properties of a document to specifically match terms in (e.g. “match
-     *       documents where the ‘subject’ property contains ‘important’”). Example:
-     *       subject:important matches documents with the term ‘important’ in the ‘subject’ property
-     *   <li>Schema type restricts
-     *       <p>This is similar to property restricts, but allows for restricts on top-level
-     *       document fields, such as schema_type. Clients should be able to limit their query to
-     *       documents of a certain schema_type (e.g. “match documents that are of the ‘Email’
-     *       schema_type”). Example: { schema_type_filters: “Email”, “Video”,query: “dog” } will
-     *       match documents that contain the query term ‘dog’ and are of either the ‘Email’ schema
-     *       type or the ‘Video’ schema type.
-     * </ul>
+     * <p>Document access can also be granted to system UIs by specifying {@link
+     * SetSchemaRequest.Builder#setSchemaTypeVisibilityForSystemUi}, or {@link
+     * SetSchemaRequest.Builder#setDocumentClassVisibilityForSystemUi} when building a schema.
+     *
+     * <p>See {@link AppSearchSessionShim#search(String, SearchSpec)} for a detailed explanation on
+     * forming a query string.
      *
      * <p>This method is lightweight. The heavy work will be done in {@link
      * SearchResultsShim#getNextPage()}.
      *
-     * @param queryExpression Query String to search.
-     * @param searchSpec Spec for setting filters, raw query etc.
-     * @return The search result of performing this operation.
+     * @param queryExpression query string to search.
+     * @param searchSpec spec for setting document filters, adding projection, setting term match
+     *     type, etc.
+     * @return a {@link SearchResultsShim} object for retrieved matched documents.
      */
     @NonNull
     SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
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 82e967a..3cefe65 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -64,7 +64,7 @@
  * and which {@link JobServiceContext} to run each job on.
  */
 class JobConcurrencyManager {
-    private static final String TAG = JobSchedulerService.TAG;
+    private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
 
     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
@@ -321,13 +321,14 @@
         }
     }
 
+    /** Return {@code true} if the state was updated. */
     @GuardedBy("mLock")
-    private void refreshSystemStateLocked() {
+    private boolean refreshSystemStateLocked() {
         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
 
         // Only refresh the information every so often.
         if (nowUptime < mNextSystemStateRefreshTime) {
-            return;
+            return false;
         }
 
         final long start = mStatLogger.getTime();
@@ -340,11 +341,14 @@
         }
 
         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
+        return true;
     }
 
     @GuardedBy("mLock")
     private void updateCounterConfigLocked() {
-        refreshSystemStateLocked();
+        if (!refreshSystemStateLocked()) {
+            return;
+        }
 
         final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
                 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
@@ -437,9 +441,10 @@
             // (sharing the same Uid as nextPending)
             int minPriorityForPreemption = Integer.MAX_VALUE;
             int selectedContextId = -1;
-            int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+            int allWorkTypes = getJobWorkTypes(nextPending);
+            int workType = mWorkCountTracker.canJobStart(allWorkTypes);
             boolean startingJob = false;
-            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+            for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
                 JobStatus job = contextIdToJobMap[j];
                 int preferredUid = preferredUidForContext[j];
                 if (job == null) {
@@ -483,7 +488,7 @@
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
                 workTypeForContext[selectedContextId] = workType;
-                mWorkCountTracker.stageJob(workType);
+                mWorkCountTracker.stageJob(workType, allWorkTypes);
             }
         }
         if (DEBUG) {
@@ -578,8 +583,10 @@
 
             JobStatus highestPriorityJob = null;
             int highPriWorkType = workType;
+            int highPriAllWorkTypes = workType;
             JobStatus backupJob = null;
             int backupWorkType = WORK_TYPE_NONE;
+            int backupAllWorkTypes = WORK_TYPE_NONE;
             for (int i = 0; i < pendingJobs.size(); i++) {
                 final JobStatus nextPending = pendingJobs.get(i);
 
@@ -589,11 +596,12 @@
 
                 if (worker.getPreferredUid() != nextPending.getUid()) {
                     if (backupJob == null) {
-                        int workAsType =
-                                mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                        int allWorkTypes = getJobWorkTypes(nextPending);
+                        int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                         if (workAsType != WORK_TYPE_NONE) {
                             backupJob = nextPending;
                             backupWorkType = workAsType;
+                            backupAllWorkTypes = allWorkTypes;
                         }
                     }
                     continue;
@@ -611,7 +619,8 @@
                 // reserved slots. We should just run the highest priority job we can find,
                 // though it would be ideal to use an available WorkType slot instead of
                 // overloading slots.
-                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                highPriAllWorkTypes = getJobWorkTypes(nextPending);
+                final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
                     // Just use the preempted job's work type since this new one is technically
                     // replacing it anyway.
@@ -624,7 +633,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Running job " + jobStatus + " as preemption");
                 }
-                mWorkCountTracker.stageJob(highPriWorkType);
+                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
             } else {
                 if (DEBUG) {
@@ -635,7 +644,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Running job " + jobStatus + " instead");
                     }
-                    mWorkCountTracker.stageJob(backupWorkType);
+                    mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
                     startJobLocked(worker, backupJob, backupWorkType);
                 }
             }
@@ -647,6 +656,7 @@
             // find.
             JobStatus highestPriorityJob = null;
             int highPriWorkType = workType;
+            int highPriAllWorkTypes = workType;
             for (int i = 0; i < pendingJobs.size(); i++) {
                 final JobStatus nextPending = pendingJobs.get(i);
 
@@ -654,7 +664,8 @@
                     continue;
                 }
 
-                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                final int allWorkTypes = getJobWorkTypes(nextPending);
+                final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
                 if (workAsType == WORK_TYPE_NONE) {
                     continue;
                 }
@@ -663,6 +674,7 @@
                         < nextPending.lastEvaluatedPriority) {
                     highestPriorityJob = nextPending;
                     highPriWorkType = workAsType;
+                    highPriAllWorkTypes = allWorkTypes;
                 }
             }
 
@@ -672,7 +684,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "About to run job: " + jobStatus);
                 }
-                mWorkCountTracker.stageJob(highPriWorkType);
+                mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
             }
         }
@@ -1102,26 +1114,58 @@
         }
 
         void incrementPendingJobCount(int workTypes) {
-            // We don't know which type we'll classify the job as when we run it yet, so make sure
-            // we have space in all applicable slots.
-            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
-                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
-            }
-            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
-                mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + 1);
-            }
-            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
-                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
-            }
-            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
-                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + 1);
+            adjustPendingJobCount(workTypes, true);
+        }
+
+        void decrementPendingJobCount(int workTypes) {
+            if (adjustPendingJobCount(workTypes, false) > 1) {
+                // We don't need to adjust reservations if only one work type was modified
+                // because that work type is the one we're using.
+
+                // 0 is WORK_TYPE_NONE.
+                int workType = 1;
+                int rem = workTypes;
+                while (rem > 0) {
+                    if ((rem & 1) != 0) {
+                        maybeAdjustReservations(workType);
+                    }
+                    rem = rem >>> 1;
+                    workType = workType << 1;
+                }
             }
         }
 
-        void stageJob(@WorkType int workType) {
+        /** Returns the number of WorkTypes that were modified. */
+        private int adjustPendingJobCount(int workTypes, boolean add) {
+            final int adj = add ? 1 : -1;
+
+            int numAdj = 0;
+            // We don't know which type we'll classify the job as when we run it yet, so make sure
+            // we have space in all applicable slots.
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_EJ) == WORK_TYPE_EJ) {
+                mNumPendingJobs.put(WORK_TYPE_EJ, mNumPendingJobs.get(WORK_TYPE_EJ) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + adj);
+                numAdj++;
+            }
+            if ((workTypes & WORK_TYPE_BGUSER) == WORK_TYPE_BGUSER) {
+                mNumPendingJobs.put(WORK_TYPE_BGUSER, mNumPendingJobs.get(WORK_TYPE_BGUSER) + adj);
+                numAdj++;
+            }
+
+            return numAdj;
+        }
+
+        void stageJob(@WorkType int workType, int allWorkTypes) {
             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
             mNumStartingJobs.put(workType, newNumStartingJobs);
-            mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+            decrementPendingJobCount(allWorkTypes);
             if (newNumStartingJobs + mNumRunningJobs.get(workType)
                     > mNumActuallyReservedSlots.get(workType)) {
                 mNumUnspecializedRemaining--;
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 bfc153f..96f3bcc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -212,7 +212,7 @@
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
     final JobConcurrencyManager mConcurrencyManager;
 
-    static final int MSG_JOB_EXPIRED = 0;
+    static final int MSG_CHECK_INDIVIDUAL_JOB = 0;
     static final int MSG_CHECK_JOB = 1;
     static final int MSG_STOP_JOB = 2;
     static final int MSG_CHECK_JOB_GREEDY = 3;
@@ -1711,6 +1711,12 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
+            JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+            if (newJs != null) {
+                // This job was stopped because the app scheduled a new job with the same job ID.
+                // Check if the new job is ready to run.
+                mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget();
+            }
             return;
         }
 
@@ -1748,7 +1754,11 @@
 
     @Override
     public void onRunJobNow(JobStatus jobStatus) {
-        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
+        if (jobStatus == null) {
+            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
+        } else {
+            mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget();
+        }
     }
 
     final private class JobHandler extends Handler {
@@ -1764,18 +1774,15 @@
                     return;
                 }
                 switch (message.what) {
-                    case MSG_JOB_EXPIRED: {
-                        JobStatus runNow = (JobStatus) message.obj;
-                        // runNow can be null, which is a controller's way of indicating that its
-                        // state is such that all ready jobs should be run immediately.
-                        if (runNow != null) {
-                            if (!isCurrentlyActiveLocked(runNow)
-                                    && isReadyToBeExecutedLocked(runNow)) {
-                                mJobPackageTracker.notePending(runNow);
-                                addOrderedItem(mPendingJobs, runNow, sPendingJobComparator);
+                    case MSG_CHECK_INDIVIDUAL_JOB: {
+                        JobStatus js = (JobStatus) message.obj;
+                        if (js != null) {
+                            if (isReadyToBeExecutedLocked(js)) {
+                                mJobPackageTracker.notePending(js);
+                                addOrderedItem(mPendingJobs, js, sPendingJobComparator);
                             }
                         } else {
-                            queueReadyJobsForExecutionLocked();
+                            Slog.e(TAG, "Given null job to check individually");
                         }
                     } break;
                     case MSG_CHECK_JOB:
@@ -1909,12 +1916,10 @@
         // This method will check and capture all ready jobs, so we don't need to keep any messages
         // in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB_GREEDY);
+        mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB);
         // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready
         // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue.
         mHandler.removeMessages(MSG_CHECK_JOB);
-        // This method will capture all expired jobs that are ready, so there's no need to keep
-        // the _EXPIRED messages in the queue.
-        mHandler.removeMessages(MSG_JOB_EXPIRED);
         if (DEBUG) {
             Slog.d(TAG, "queuing all ready jobs for execution:");
         }
@@ -1990,8 +1995,11 @@
                 }
 
                 final boolean shouldForceBatchJob;
-                // Restricted jobs must always be batched
-                if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+                if (job.shouldTreatAsExpeditedJob()) {
+                    // Never batch expedited jobs, even for RESTRICTED apps.
+                    shouldForceBatchJob = false;
+                } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
+                    // Restricted jobs must always be batched
                     shouldForceBatchJob = true;
                 } else if (job.getNumFailures() > 0) {
                     shouldForceBatchJob = false;
@@ -3175,7 +3183,7 @@
                     TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
                     pw.println();
                     job.dump(pw, "    ", false, nowElapsed);
-                    int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
+                    int priority = evaluateJobPriorityLocked(job);
                     pw.print("    Evaluated priority: ");
                     pw.println(JobInfo.getPriorityString(priority));
 
@@ -3341,7 +3349,7 @@
                     job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
 
                     proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
-                            evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
+                            evaluateJobPriorityLocked(job));
 
                     proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
                             nowUptime - job.madeActive);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d15bae0..da6f9fe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -20,6 +20,7 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.Nullable;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
 import android.app.job.JobInfo;
@@ -326,6 +327,7 @@
     /**
      * Used externally to query the running job. Will return null if there is no job running.
      */
+    @Nullable
     JobStatus getRunningJobLocked() {
         return mRunningJob;
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 50723c7..131a6d4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -107,8 +107,6 @@
                         taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
                     }
                 }
-                taskStatus.changedAuthorities = null;
-                taskStatus.changedUris = null;
             }
             taskStatus.changedAuthorities = null;
             taskStatus.changedUris = null;
diff --git a/apex/media/Android.bp b/apex/media/Android.bp
index 5f1bd37..d6666f5 100644
--- a/apex/media/Android.bp
+++ b/apex/media/Android.bp
@@ -18,3 +18,10 @@
         "//frameworks/av/apex/testing",
     ],
 }
+
+sdk {
+    name: "media-module-sdk",
+    java_sdk_libs: [
+        "framework-media",
+    ],
+}
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 67fa9bb..a2366df 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -8,9 +8,10 @@
     method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes();
     method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes();
     method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
-    method public boolean isHdrTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+    method public boolean isFormatSpecified(@NonNull String);
+    method public boolean isHdrTypeSupported(@NonNull String);
     method public boolean isSlowMotionSupported();
-    method public boolean isVideoMimeTypeSupported(@NonNull String) throws android.media.ApplicationMediaCapabilities.FormatNotFoundException;
+    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;
   }
@@ -24,10 +25,6 @@
     method @NonNull public android.media.ApplicationMediaCapabilities build();
   }
 
-  public static class ApplicationMediaCapabilities.FormatNotFoundException extends android.util.AndroidException {
-    ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String);
-  }
-
   public class MediaCommunicationManager {
     method @IntRange(from=1) public int getVersion();
   }
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index aefeab6..685cf0d 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.AndroidException;
 import android.util.Log;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -79,17 +78,7 @@
 public final class ApplicationMediaCapabilities implements Parcelable {
     private static final String TAG = "ApplicationMediaCapabilities";
 
-    /**
-     * This exception is thrown when a given format is not specified in the media capabilities.
-     */
-    public static class FormatNotFoundException extends AndroidException {
-        public FormatNotFoundException(@NonNull String format) {
-            super(format);
-        }
-    }
-
     /** List of supported video codec mime types. */
-    // TODO: init it with avc and mpeg4 as application is assuming to support them.
     private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
 
     /** List of unsupported video codec mime types. */
@@ -113,39 +102,54 @@
 
     /**
      * Query if a video codec format is supported by the application.
+     * <p>
+     * If the application has not specified supporting the format or not, this will return false.
+     * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+     *
      * @param videoMime The mime type of the video codec format. Must be the one used in
      * {@link MediaFormat#KEY_MIME}.
      * @return true if application supports the video codec format, false otherwise.
-     * @throws FormatNotFoundException if the application did not specify the codec either in the
-     * supported or unsupported formats.
      */
     public boolean isVideoMimeTypeSupported(
-            @NonNull String videoMime) throws FormatNotFoundException {
-        if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
-            return false;
-        } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
+            @NonNull String videoMime) {
+        if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
             return true;
-        } else {
-            throw new FormatNotFoundException(videoMime);
         }
+        return false;
     }
 
     /**
      * Query if a HDR type is supported by the application.
+     * <p>
+     * If the application has not specified supporting the format or not, this will return false.
+     * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
+     *
      * @param hdrType The type of the HDR format.
      * @return true if application supports the HDR format, false otherwise.
-     * @throws FormatNotFoundException if the application did not specify the format either in the
-     * supported or unsupported formats.
      */
     public boolean isHdrTypeSupported(
-            @NonNull @MediaFeature.MediaHdrType String hdrType) throws FormatNotFoundException {
-        if (mUnsupportedHdrTypes.contains(hdrType)) {
-            return false;
-        } else if (mSupportedHdrTypes.contains(hdrType)) {
+            @NonNull @MediaFeature.MediaHdrType String hdrType) {
+        if (mSupportedHdrTypes.contains(hdrType)) {
             return true;
-        } else {
-            throw new FormatNotFoundException(hdrType);
         }
+        return false;
+    }
+
+    /**
+     * Query if a format is specified by the application.
+     * <p>
+     * The format could be either the video format or the hdr format.
+     *
+     * @param format The name of the format.
+     * @return true if application specifies the format, false otherwise.
+     */
+    public boolean isFormatSpecified(@NonNull String format) {
+        if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format)
+                || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) {
+            return true;
+
+        }
+        return false;
     }
 
     @Override
diff --git a/apex/media/framework/java/android/media/MediaTranscodeManager.java b/apex/media/framework/java/android/media/MediaTranscodeManager.java
index ce7726a..c924d9a 100644
--- a/apex/media/framework/java/android/media/MediaTranscodeManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodeManager.java
@@ -1062,14 +1062,8 @@
                             "Source video format hint must be set!");
                 }
 
-                boolean supportHevc = false;
-                try {
-                    supportHevc = mClientCaps.isVideoMimeTypeSupported(
-                            MediaFormat.MIMETYPE_VIDEO_HEVC);
-                } catch (ApplicationMediaCapabilities.FormatNotFoundException ex) {
-                    // Set to false if application did not specify.
-                    supportHevc = false;
-                }
+                boolean supportHevc = mClientCaps.isVideoMimeTypeSupported(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC);
                 if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals(
                         mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) {
                     return true;
diff --git a/api/Android.bp b/api/Android.bp
index 69dce97..ac2f083 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -28,6 +28,7 @@
 genrule {
     name: "frameworks-base-api-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.public.api.txt}",
         ":art.module.public.api{.public.api.txt}",
         ":conscrypt.module.public.api{.public.api.txt}",
         ":framework-appsearch{.public.api.txt}",
@@ -36,6 +37,7 @@
         ":framework-mediaprovider{.public.api.txt}",
         ":framework-permission{.public.api.txt}",
         ":framework-permission-s{.public.api.txt}",
+        ":framework-scheduling{.public.api.txt}",
         ":framework-sdkextensions{.public.api.txt}",
         ":framework-statsd{.public.api.txt}",
         ":framework-tethering{.public.api.txt}",
@@ -64,6 +66,7 @@
 genrule {
     name: "frameworks-base-api-current.srcjar",
     srcs: [
+        ":android.net.ipsec.ike{.public.stubs.source}",
         ":api-stubs-docs-non-updatable",
         ":art.module.public.api{.public.stubs.source}",
         ":conscrypt.module.public.api{.public.stubs.source}",
@@ -73,6 +76,7 @@
         ":framework-mediaprovider{.public.stubs.source}",
         ":framework-permission{.public.stubs.source}",
         ":framework-permission-s{.public.stubs.source}",
+        ":framework-scheduling{.public.stubs.source}",
         ":framework-sdkextensions{.public.stubs.source}",
         ":framework-statsd{.public.stubs.source}",
         ":framework-tethering{.public.stubs.source}",
@@ -88,6 +92,7 @@
 genrule {
     name: "frameworks-base-api-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.public.removed-api.txt}",
         ":art.module.public.api{.public.removed-api.txt}",
         ":conscrypt.module.public.api{.public.removed-api.txt}",
         ":framework-appsearch{.public.removed-api.txt}",
@@ -96,6 +101,7 @@
         ":framework-mediaprovider{.public.removed-api.txt}",
         ":framework-permission{.public.removed-api.txt}",
         ":framework-permission-s{.public.removed-api.txt}",
+        ":framework-scheduling{.public.removed-api.txt}",
         ":framework-sdkextensions{.public.removed-api.txt}",
         ":framework-statsd{.public.removed-api.txt}",
         ":framework-tethering{.public.removed-api.txt}",
@@ -123,12 +129,14 @@
 genrule {
     name: "frameworks-base-api-system-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.system.api.txt}",
         ":framework-appsearch{.system.api.txt}",
         ":framework-graphics{.system.api.txt}",
         ":framework-media{.system.api.txt}",
         ":framework-mediaprovider{.system.api.txt}",
         ":framework-permission{.system.api.txt}",
         ":framework-permission-s{.system.api.txt}",
+        ":framework-scheduling{.system.api.txt}",
         ":framework-sdkextensions{.system.api.txt}",
         ":framework-statsd{.system.api.txt}",
         ":framework-tethering{.system.api.txt}",
@@ -156,12 +164,14 @@
 genrule {
     name: "frameworks-base-api-system-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.system.removed-api.txt}",
         ":framework-appsearch{.system.removed-api.txt}",
         ":framework-graphics{.system.removed-api.txt}",
         ":framework-media{.system.removed-api.txt}",
         ":framework-mediaprovider{.system.removed-api.txt}",
         ":framework-permission{.system.removed-api.txt}",
         ":framework-permission-s{.system.removed-api.txt}",
+        ":framework-scheduling{.system.removed-api.txt}",
         ":framework-sdkextensions{.system.removed-api.txt}",
         ":framework-statsd{.system.removed-api.txt}",
         ":framework-tethering{.system.removed-api.txt}",
@@ -189,12 +199,14 @@
 genrule {
     name: "frameworks-base-api-module-lib-current.txt",
     srcs: [
+        ":android.net.ipsec.ike{.module-lib.api.txt}",
         ":framework-appsearch{.module-lib.api.txt}",
         ":framework-graphics{.module-lib.api.txt}",
         ":framework-media{.module-lib.api.txt}",
         ":framework-mediaprovider{.module-lib.api.txt}",
         ":framework-permission{.module-lib.api.txt}",
         ":framework-permission-s{.module-lib.api.txt}",
+        ":framework-scheduling{.module-lib.api.txt}",
         ":framework-sdkextensions{.module-lib.api.txt}",
         ":framework-statsd{.module-lib.api.txt}",
         ":framework-tethering{.module-lib.api.txt}",
@@ -221,12 +233,14 @@
 genrule {
     name: "frameworks-base-api-module-lib-removed.txt",
     srcs: [
+        ":android.net.ipsec.ike{.module-lib.removed-api.txt}",
         ":framework-appsearch{.module-lib.removed-api.txt}",
         ":framework-graphics{.module-lib.removed-api.txt}",
         ":framework-media{.module-lib.removed-api.txt}",
         ":framework-mediaprovider{.module-lib.removed-api.txt}",
         ":framework-permission{.module-lib.removed-api.txt}",
         ":framework-permission-s{.module-lib.removed-api.txt}",
+        ":framework-scheduling{.module-lib.removed-api.txt}",
         ":framework-sdkextensions{.module-lib.removed-api.txt}",
         ":framework-statsd{.module-lib.removed-api.txt}",
         ":framework-tethering{.module-lib.removed-api.txt}",
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index e21a6b2..3bad889 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -51,13 +51,18 @@
             static: {
                 enabled: false,
             },
+            static_libs: [
+                "libidmap2_protos",
+            ],
             shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libcutils",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
         host: {
@@ -68,15 +73,30 @@
                 "libandroidfw",
                 "libbase",
                 "libcutils",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libidmap2_protos",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
     },
 }
 
 cc_library {
+    name: "libidmap2_protos",
+    srcs: [
+        "libidmap2/proto/*.proto",
+    ],
+    host_supported: true,
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
+}
+
+cc_library {
     name: "libidmap2_policies",
     defaults: [
         "idmap2_defaults",
@@ -88,9 +108,6 @@
             enabled: true,
         },
         android: {
-            static: {
-                enabled: false,
-            },
             shared_libs: [
                 "libandroidfw",
             ],
@@ -119,6 +136,7 @@
     srcs: [
         "tests/BinaryStreamVisitorTests.cpp",
         "tests/CommandLineOptionsTests.cpp",
+        "tests/FabricatedOverlayTests.cpp",
         "tests/FileUtilsTests.cpp",
         "tests/Idmap2BinaryTests.cpp",
         "tests/IdmapTests.cpp",
@@ -130,23 +148,27 @@
         "tests/ResourceUtilsTests.cpp",
         "tests/ResultTests.cpp",
         "tests/XmlParserTests.cpp",
-        "tests/ZipFileTests.cpp",
     ],
     required: [
         "idmap2",
     ],
-    static_libs: ["libgmock"],
+    static_libs: [
+        "libgmock",
+        "libidmap2_protos",
+    ],
     target: {
         android: {
             shared_libs: [
                 "libandroidfw",
                 "libbase",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libz",
+                "libz",
                 "libziparchive",
-                "libidmap2_policies",
             ],
         },
         host: {
@@ -155,17 +177,28 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
-                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
             ],
+            data: [
+                ":libz",
+                ":idmap2",
+            ],
         },
     },
-    data: ["tests/data/**/*.apk"],
+    data: [
+        "tests/data/**/*.apk",
+    ],
+    compile_multilib: "first",
+    test_options: {
+        unit_test: true,
+    },
 }
 
 cc_binary {
@@ -182,6 +215,9 @@
         "idmap2/Lookup.cpp",
         "idmap2/Main.cpp",
     ],
+    static_libs: [
+        "libidmap2_protos",
+    ],
     target: {
         android: {
             shared_libs: [
@@ -189,9 +225,11 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
-                "libutils",
-                "libziparchive",
                 "libidmap2_policies",
+                "libprotobuf-cpp-lite",
+                "libutils",
+                "libz",
+                "libziparchive",
             ],
         },
         host: {
@@ -200,10 +238,11 @@
                 "libbase",
                 "libcutils",
                 "libidmap2",
+                "libidmap2_policies",
                 "liblog",
+                "libprotobuf-cpp-lite",
                 "libutils",
                 "libziparchive",
-                "libidmap2_policies",
             ],
             shared_libs: [
                 "libz",
@@ -229,11 +268,14 @@
         "libbinder",
         "libcutils",
         "libidmap2",
+        "libidmap2_policies",
+        "libprotobuf-cpp-lite",
         "libutils",
         "libziparchive",
-        "libidmap2_policies",
     ],
     static_libs: [
+        "libc++fs",
+        "libidmap2_protos",
         "libidmap2daidl",
     ],
     init_rc: ["idmap2d/idmap2d.rc"],
@@ -241,28 +283,41 @@
 
 cc_library_static {
     name: "libidmap2daidl",
-    defaults: [
-        "idmap2_defaults",
-    ],
-    tidy: false,
-    host_supported: false,
     srcs: [
         ":idmap2_aidl",
+        ":idmap2_core_aidl",
+    ],
+    header_libs: [
+        "libbinder_headers",
     ],
     shared_libs: [
         "libbase",
     ],
     aidl: {
         export_aidl_headers: true,
+        local_include_dirs: [
+            "idmap2d/aidl/core",
+            "idmap2d/aidl/services/",
+        ],
     },
 }
 
 filegroup {
+    name: "idmap2_core_aidl",
+    srcs: [
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl",
+        "idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl",
+    ],
+    path: "idmap2d/aidl/core/",
+}
+
+filegroup {
     name: "idmap2_aidl",
     srcs: [
-        "idmap2d/aidl/android/os/IIdmap2.aidl",
+        "idmap2d/aidl/services/android/os/IIdmap2.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
 
 aidl_interface {
@@ -274,7 +329,7 @@
 filegroup {
     name: "overlayable_policy_aidl_files",
     srcs: [
-        "idmap2d/aidl/android/os/OverlayablePolicy.aidl",
+        "idmap2d/aidl/services/android/os/OverlayablePolicy.aidl",
     ],
-    path: "idmap2d/aidl",
+    path: "idmap2d/aidl/services/",
 }
diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml
deleted file mode 100644
index 5147f4e..0000000
--- a/cmds/idmap2/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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 idmap2_tests">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" />
-    </target_preparer>
-    <option name="test-suite-tag" value="idmap2_tests" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="idmap2_tests" />
-    </test>
-</configuration>
diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp
index 09867f3..bf30a76 100644
--- a/cmds/idmap2/idmap2/CommandUtils.cpp
+++ b/cmds/idmap2/idmap2/CommandUtils.cpp
@@ -25,7 +25,9 @@
 
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 
 Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path,
@@ -39,11 +41,20 @@
     return Error("failed to parse idmap header");
   }
 
-  const auto header_ok = header->IsUpToDate(target_path, overlay_path, overlay_name,
-                                            fulfilled_policies, enforce_overlayable);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_path.c_str());
+  }
+
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error("failed to load overlay '%s'", overlay_path.c_str());
+  }
+
+  const auto header_ok = header->IsUpToDate(**target, **overlay, overlay_name, fulfilled_policies,
+                                            enforce_overlayable);
   if (!header_ok) {
     return Error(header_ok.GetError(), "idmap not up to date");
   }
-
   return Unit{};
 }
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c93c717..977a0bb 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -20,7 +20,6 @@
 #include <fstream>
 #include <memory>
 #include <ostream>
-#include <string>
 #include <vector>
 
 #include "androidfw/ResourceTypes.h"
@@ -31,12 +30,13 @@
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/SysTrace.h"
 
-using android::ApkAssets;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 using android::idmap2::utils::kIdmapFilePermissionMask;
 using android::idmap2::utils::PoliciesToBitmaskResult;
@@ -93,18 +93,18 @@
     fulfilled_policies |= PolicyFlags::PUBLIC;
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error("failed to load apk %s", target_apk_path.c_str());
+  const auto target = TargetResourceContainer::FromPath(target_apk_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_apk_path.c_str());
   }
 
-  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error("failed to load apk %s", overlay_apk_path.c_str());
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  if (!overlay) {
+    return Error("failed to load apk overlay '%s'", overlay_apk_path.c_str());
   }
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, overlay_name,
-                                          fulfilled_policies, !ignore_overlayable);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, overlay_name, fulfilled_policies,
+                                           !ignore_overlayable);
   if (!idmap) {
     return Error(idmap.GetError(), "failed to create idmap");
   }
@@ -112,13 +112,14 @@
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
-    return Error("failed to open idmap path %s", idmap_path.c_str());
+    return Error("failed to open idmap path '%s'", idmap_path.c_str());
   }
+
   BinaryStreamVisitor visitor(fout);
   (*idmap)->accept(&visitor);
   fout.close();
   if (fout.fail()) {
-    return Error("failed to write to idmap path %s", idmap_path.c_str());
+    return Error("failed to write to idmap path '%s'", idmap_path.c_str());
   }
 
   return Unit{};
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
index 5db391c..953d99f 100644
--- a/cmds/idmap2/idmap2/CreateMultiple.cpp
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -34,13 +34,14 @@
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/SysTrace.h"
 
-using android::ApkAssets;
 using android::base::StringPrintf;
 using android::idmap2::BinaryStreamVisitor;
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::Idmap;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::Result;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::Unit;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
@@ -91,9 +92,9 @@
     fulfilled_policies |= PolicyFlags::PUBLIC;
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error("failed to load apk %s", target_apk_path.c_str());
+  const auto target = TargetResourceContainer::FromPath(target_apk_path);
+  if (!target) {
+    return Error("failed to load target '%s'", target_apk_path.c_str());
   }
 
   std::vector<std::string> idmap_paths;
@@ -108,14 +109,14 @@
     // TODO(b/175014391): Support multiple overlay tags in OverlayConfig
     if (!Verify(idmap_path, target_apk_path, overlay_apk_path, "", fulfilled_policies,
                 !ignore_overlayable)) {
-      const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-      if (!overlay_apk) {
+      const auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+      if (!overlay) {
         LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
         continue;
       }
 
-      const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", fulfilled_policies,
-                                              !ignore_overlayable);
+      const auto idmap =
+          Idmap::FromContainers(**target, **overlay, "", fulfilled_policies, !ignore_overlayable);
       if (!idmap) {
         LOG(WARNING) << "failed to create idmap";
         continue;
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index 43a1951..f41e57c 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -37,7 +37,6 @@
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
 #include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 #include "utils/String16.h"
 #include "utils/String8.h"
 
@@ -52,10 +51,10 @@
 using android::idmap2::CommandLineOptions;
 using android::idmap2::Error;
 using android::idmap2::IdmapHeader;
+using android::idmap2::OverlayResourceContainer;
 using android::idmap2::ResourceId;
 using android::idmap2::Result;
 using android::idmap2::Unit;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
 
 namespace {
 
@@ -195,12 +194,17 @@
       }
       apk_assets.push_back(std::move(target_apk));
 
-      auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath(),
-                                                      idmap_header->GetOverlayName());
+      auto overlay = OverlayResourceContainer::FromPath(idmap_header->GetOverlayPath());
+      if (!overlay) {
+        return overlay.GetError();
+      }
+
+      auto manifest_info = (*overlay)->FindOverlayInfo(idmap_header->GetOverlayName());
       if (!manifest_info) {
         return manifest_info.GetError();
       }
-      target_package_name = manifest_info->target_package;
+
+      target_package_name = (*manifest_info).target_package;
     } else if (target_path != idmap_header->GetTargetPath()) {
       return Error("different target APKs (expected target APK %s but %s has target APK %s)",
                    target_path.c_str(), idmap_path.c_str(), idmap_header->GetTargetPath().c_str());
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 93537d3..05336ba 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -18,10 +18,10 @@
 
 #include <sys/stat.h>   // umask
 #include <sys/types.h>  // umask
-#include <unistd.h>
 
 #include <cerrno>
 #include <cstring>
+#include <filesystem>
 #include <fstream>
 #include <memory>
 #include <ostream>
@@ -35,19 +35,20 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String8.h"
 
 using android::IPCThreadState;
 using android::base::StringPrintf;
 using android::binder::Status;
 using android::idmap2::BinaryStreamVisitor;
-using android::idmap2::GetPackageCrc;
+using android::idmap2::FabricatedOverlay;
+using android::idmap2::FabricatedOverlayContainer;
 using android::idmap2::Idmap;
 using android::idmap2::IdmapHeader;
-using android::idmap2::ZipFile;
+using android::idmap2::OverlayResourceContainer;
+using android::idmap2::TargetResourceContainer;
 using android::idmap2::utils::kIdmapCacheDir;
 using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::RandomStringForPath;
 using android::idmap2::utils::UidHasWriteAccessToPath;
 
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
@@ -69,39 +70,24 @@
   return static_cast<PolicyBitmask>(arg);
 }
 
-Status GetCrc(const std::string& apk_path, uint32_t* out_crc) {
-  const auto zip = ZipFile::Open(apk_path);
-  if (!zip) {
-    return error(StringPrintf("failed to open apk %s", apk_path.c_str()));
-  }
-
-  const auto crc = GetPackageCrc(*zip);
-  if (!crc) {
-    return error(crc.GetErrorMessage());
-  }
-
-  *out_crc = *crc;
-  return ok();
-}
-
 }  // namespace
 
 namespace android::os {
 
-Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
+Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
                                    int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_apk_path;
-  *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  SYSTRACE << "Idmap2Service::getIdmapPath " << overlay_path;
+  *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   return ok();
 }
 
-Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
-                                  int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+Status Idmap2Service::removeIdmap(const std::string& overlay_path, int32_t user_id ATTRIBUTE_UNUSED,
+                                  bool* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::removeIdmap " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::removeIdmap " << overlay_path;
   const uid_t uid = IPCThreadState::self()->getCallingUid();
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   if (!UidHasWriteAccessToPath(uid, idmap_path)) {
     *_aidl_return = false;
     return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
@@ -115,93 +101,88 @@
   return ok();
 }
 
-Status Idmap2Service::verifyIdmap(const std::string& target_apk_path,
-                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                   bool* _aidl_return) {
-  SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_path;
   assert(_aidl_return);
 
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   std::ifstream fin(idmap_path);
   const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
   fin.close();
   if (!header) {
     *_aidl_return = false;
-    return error("failed to parse idmap header");
+    LOG(WARNING) << "failed to parse idmap header of '" << idmap_path << "'";
+    return ok();
   }
 
-  uint32_t target_crc;
-  if (target_apk_path == kFrameworkPath && android_crc_) {
-    target_crc = *android_crc_;
-  } else {
-    auto target_crc_status = GetCrc(target_apk_path, &target_crc);
-    if (!target_crc_status.isOk()) {
-      *_aidl_return = false;
-      return target_crc_status;
-    }
-
-    // Loading the framework zip can take several milliseconds. Cache the crc of the framework
-    // resource APK to reduce repeated work during boot.
-    if (target_apk_path == kFrameworkPath) {
-      android_crc_ = target_crc;
-    }
-  }
-
-  uint32_t overlay_crc;
-  auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc);
-  if (!overlay_crc_status.isOk()) {
+  const auto target = GetTargetContainer(target_path);
+  if (!target) {
     *_aidl_return = false;
-    return overlay_crc_status;
+    LOG(WARNING) << "failed to load target '" << target_path << "'";
+    return ok();
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d verify
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    *_aidl_return = false;
+    LOG(WARNING) << "failed to load overlay '" << overlay_path << "'";
+    return ok();
+  }
+
   auto up_to_date =
-      header->IsUpToDate(target_apk_path, overlay_apk_path, "", target_crc, overlay_crc,
+      header->IsUpToDate(*GetPointer(*target), **overlay, overlay_name,
                          ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable);
 
   *_aidl_return = static_cast<bool>(up_to_date);
-  return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage());
+  if (!up_to_date) {
+    LOG(WARNING) << "idmap '" << idmap_path
+                 << "' not up to date : " << up_to_date.GetErrorMessage();
+  }
+  return ok();
 }
 
-Status Idmap2Service::createIdmap(const std::string& target_apk_path,
-                                  const std::string& overlay_apk_path, int32_t fulfilled_policies,
+Status Idmap2Service::createIdmap(const std::string& target_path, const std::string& overlay_path,
+                                  const std::string& overlay_name, int32_t fulfilled_policies,
                                   bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED,
                                   std::optional<std::string>* _aidl_return) {
   assert(_aidl_return);
-  SYSTRACE << "Idmap2Service::createIdmap " << target_apk_path << " " << overlay_apk_path;
+  SYSTRACE << "Idmap2Service::createIdmap " << target_path << " " << overlay_path;
   _aidl_return->reset();
 
   const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
 
-  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
   const uid_t uid = IPCThreadState::self()->getCallingUid();
   if (!UidHasWriteAccessToPath(uid, idmap_path)) {
     return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
                                     idmap_path.c_str(), uid));
   }
 
-  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return error("failed to load apk " + target_apk_path);
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected. The file must be deleted
+  // before attempting to create the idmap, so that if idmap  creation fails, the overlay will no
+  // longer be usable.
+  unlink(idmap_path.c_str());
+
+  const auto target = GetTargetContainer(target_path);
+  if (!target) {
+    return error("failed to load target '%s'" + target_path);
   }
 
-  const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return error("failed to load apk " + overlay_apk_path);
+  const auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return error("failed to load apk overlay '%s'" + overlay_path);
   }
 
-  // TODO(162841629): Support passing overlay name to idmap2d create
-  const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, "", policy_bitmask, enforce_overlayable);
+  const auto idmap = Idmap::FromContainers(*GetPointer(*target), **overlay, overlay_name,
+                                           policy_bitmask, enforce_overlayable);
   if (!idmap) {
     return error(idmap.GetErrorMessage());
   }
 
-  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
-  // that existing memory maps will continue to be valid and unaffected.
-  unlink(idmap_path.c_str());
-
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
@@ -220,4 +201,155 @@
   return ok();
 }
 
+idmap2::Result<Idmap2Service::TargetResourceContainerPtr> Idmap2Service::GetTargetContainer(
+    const std::string& target_path) {
+  if (target_path == kFrameworkPath) {
+    if (framework_apk_cache_ == nullptr) {
+      // Initialize the framework APK cache.
+      auto target = TargetResourceContainer::FromPath(target_path);
+      if (!target) {
+        return target.GetError();
+      }
+      framework_apk_cache_ = std::move(*target);
+    }
+    return {framework_apk_cache_.get()};
+  }
+
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return target.GetError();
+  }
+  return {std::move(*target)};
+}
+
+Status Idmap2Service::createFabricatedOverlay(
+    const os::FabricatedOverlayInternal& overlay,
+    std::optional<os::FabricatedOverlayInfo>* _aidl_return) {
+  idmap2::FabricatedOverlay::Builder builder(overlay.packageName, overlay.overlayName,
+                                             overlay.targetPackageName);
+  if (!overlay.targetOverlayable.empty()) {
+    builder.SetOverlayable(overlay.targetOverlayable);
+  }
+
+  for (const auto& res : overlay.entries) {
+    builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+  }
+
+  // Generate the file path of the fabricated overlay and ensure it does not collide with an
+  // existing path. Re-registering a fabricated overlay will always result in an updated path.
+  std::string path;
+  std::string file_name;
+  do {
+    constexpr size_t kSuffixLength = 4;
+    const std::string random_suffix = RandomStringForPath(kSuffixLength);
+    file_name = StringPrintf("%s-%s-%s.frro", overlay.packageName.c_str(),
+                             overlay.overlayName.c_str(), random_suffix.c_str());
+    path = StringPrintf("%s/%s", kIdmapCacheDir, file_name.c_str());
+
+    // Invoking std::filesystem::exists with a file name greater than 255 characters will cause this
+    // process to abort since the name exceeds the maximum file name size.
+    const size_t kMaxFileNameLength = 255;
+    if (file_name.size() > kMaxFileNameLength) {
+      return error(
+          base::StringPrintf("fabricated overlay file name '%s' longer than %zu characters",
+                             file_name.c_str(), kMaxFileNameLength));
+    }
+  } while (std::filesystem::exists(path));
+
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+  if (!UidHasWriteAccessToPath(uid, path)) {
+    return error(base::StringPrintf("will not write to %s: calling uid %d lacks write access",
+                                    path.c_str(), uid));
+  }
+
+  // Persist the fabricated overlay.
+  umask(kIdmapFilePermissionMask);
+  std::ofstream fout(path);
+  if (fout.fail()) {
+    return error("failed to open frro path " + path);
+  }
+  const auto frro = builder.Build();
+  if (!frro) {
+    return error(StringPrintf("failed to serialize '%s:%s': %s", overlay.packageName.c_str(),
+                              overlay.overlayName.c_str(), frro.GetErrorMessage().c_str()));
+  }
+  auto result = frro->ToBinaryStream(fout);
+  if (!result) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path + ": " + result.GetErrorMessage());
+  }
+  if (fout.fail()) {
+    unlink(path.c_str());
+    return error("failed to write to frro path " + path);
+  }
+
+  os::FabricatedOverlayInfo out_info;
+  out_info.packageName = overlay.packageName;
+  out_info.overlayName = overlay.overlayName;
+  out_info.targetPackageName = overlay.targetPackageName;
+  out_info.targetOverlayable = overlay.targetOverlayable;
+  out_info.path = path;
+  *_aidl_return = out_info;
+  return ok();
+}
+
+Status Idmap2Service::getFabricatedOverlayInfos(
+    std::vector<os::FabricatedOverlayInfo>* _aidl_return) {
+  for (const auto& entry : std::filesystem::directory_iterator(kIdmapCacheDir)) {
+    if (!android::IsFabricatedOverlay(entry.path())) {
+      continue;
+    }
+
+    const auto overlay = FabricatedOverlayContainer::FromPath(entry.path());
+    if (!overlay) {
+      // This is a sign something went wrong.
+      LOG(ERROR) << "Failed to open '" << entry.path() << "': " << overlay.GetErrorMessage();
+      continue;
+    }
+
+    const auto info = (*overlay)->GetManifestInfo();
+    os::FabricatedOverlayInfo out_info;
+    out_info.packageName = info.package_name;
+    out_info.overlayName = info.name;
+    out_info.targetPackageName = info.target_package;
+    out_info.targetOverlayable = info.target_name;
+    out_info.path = entry.path();
+    _aidl_return->emplace_back(std::move(out_info));
+  }
+
+  return ok();
+}
+
+binder::Status Idmap2Service::deleteFabricatedOverlay(const std::string& overlay_path,
+                                                      bool* _aidl_return) {
+  SYSTRACE << "Idmap2Service::deleteFabricatedOverlay " << overlay_path;
+  const uid_t uid = IPCThreadState::self()->getCallingUid();
+
+  if (!UidHasWriteAccessToPath(uid, overlay_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    overlay_path.c_str(), uid));
+  }
+
+  const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_path);
+  if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+    *_aidl_return = false;
+    return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+                                    idmap_path.c_str(), uid));
+  }
+
+  if (unlink(overlay_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + overlay_path + ": " + strerror(errno));
+  }
+
+  if (unlink(idmap_path.c_str()) != 0) {
+    *_aidl_return = false;
+    return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+  }
+
+  *_aidl_return = true;
+  return ok();
+}
+
 }  // namespace android::os
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 0127e87..4d16ff3 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -18,12 +18,14 @@
 #define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
 
 #include <android-base/unique_fd.h>
+#include <android/os/BnIdmap2.h>
+#include <android/os/FabricatedOverlayInfo.h>
 #include <binder/BinderService.h>
+#include <idmap2/ResourceContainer.h>
+#include <idmap2/Result.h>
 
 #include <string>
 
-#include "android/os/BnIdmap2.h"
-
 namespace android::os {
 
 class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
@@ -32,27 +34,58 @@
     return "idmap";
   }
 
-  binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
+  binder::Status getIdmapPath(const std::string& overlay_path, int32_t user_id,
                               std::string* _aidl_return) override;
 
-  binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
+  binder::Status removeIdmap(const std::string& overlay_path, int32_t user_id,
                              bool* _aidl_return) override;
 
-  binder::Status verifyIdmap(const std::string& target_apk_path,
-                             const std::string& overlay_apk_path, int32_t fulfilled_policies,
+  binder::Status verifyIdmap(const std::string& target_path, const std::string& overlay_path,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
                              bool* _aidl_return) override;
 
-  binder::Status createIdmap(const std::string& target_apk_path,
-                             const std::string& overlay_apk_path, int32_t fulfilled_policies,
+  binder::Status createIdmap(const std::string& target_path, const std::string& overlay_path,
+                             const std::string& overlay_name, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
                              std::optional<std::string>* _aidl_return) override;
 
+  binder::Status createFabricatedOverlay(
+      const os::FabricatedOverlayInternal& overlay,
+      std::optional<os::FabricatedOverlayInfo>* _aidl_return) override;
+
+  binder::Status deleteFabricatedOverlay(const std::string& overlay_path,
+                                         bool* _aidl_return) override;
+
+  binder::Status getFabricatedOverlayInfos(
+      std::vector<os::FabricatedOverlayInfo>* _aidl_return) override;
+
  private:
-  // Cache the crc of the android framework package since the crc cannot change without a reboot.
-  std::optional<uint32_t> android_crc_;
+  // idmap2d is killed after a period of inactivity, so any information stored on this class should
+  // be able to be recalculated if idmap2 dies and restarts.
+  std::unique_ptr<idmap2::TargetResourceContainer> framework_apk_cache_;
+
+  std::vector<os::FabricatedOverlayInfo> fabricated_overlays_;
+
+  template <typename T>
+  using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
+
+  using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
+  idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
+
+  template <typename T>
+  WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
 };
 
+template <typename T>
+const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
+  auto u = std::get_if<T*>(&ptr);
+  if (u != nullptr) {
+    return *u;
+  }
+  return std::get<std::unique_ptr<T>>(ptr).get();
+}
+
 }  // namespace android::os
 
 #endif  // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
similarity index 70%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
index 14d57bf..6375d24 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInfo.aidl
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.os;
 
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInfo {
+    @utf8InCpp String path;
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+}
\ No newline at end of file
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
similarity index 64%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
index 14d57bf..f67d8be 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternal.aidl
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.os;
 
-parcelable ExternalTimeSuggestion;
+import android.os.FabricatedOverlayInternalEntry;
+
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternal {
+    @utf8InCpp String packageName;
+    @utf8InCpp String overlayName;
+    @utf8InCpp String targetPackageName;
+    @utf8InCpp String targetOverlayable;
+    List<FabricatedOverlayInternalEntry> entries;
+}
\ No newline at end of file
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
similarity index 79%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 14d57bf..6c2af27 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.os;
 
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FabricatedOverlayInternalEntry {
+    @utf8InCpp String resourceName;
+    int dataType;
+    int data;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
similarity index 73%
rename from cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
index 156f1d7..35bca98 100644
--- a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
+++ b/cmds/idmap2/idmap2d/aidl/services/android/os/IIdmap2.aidl
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInfo;
+
 /**
  * @hide
  */
@@ -23,13 +26,18 @@
   @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
   boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
   boolean verifyIdmap(@utf8InCpp String targetApkPath,
-					  @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayApkPath,
+                      @utf8InCpp String overlayName,
                       int fulfilledPolicies,
                       boolean enforceOverlayable,
                       int userId);
   @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
                                           @utf8InCpp String overlayApkPath,
+                                          @utf8InCpp String overlayName,
                                           int fulfilledPolicies,
                                           boolean enforceOverlayable,
                                           int userId);
+  @nullable FabricatedOverlayInfo createFabricatedOverlay(in FabricatedOverlayInternal overlay);
+  List<FabricatedOverlayInfo> getFabricatedOverlayInfos();
+  boolean deleteFabricatedOverlay(@utf8InCpp String path);
 }
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl b/cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
similarity index 100%
rename from cmds/idmap2/idmap2d/aidl/android/os/OverlayablePolicy.aidl
rename to cmds/idmap2/idmap2d/aidl/services/android/os/OverlayablePolicy.aidl
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
new file mode 100644
index 0000000..be687d9
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+#define IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
+
+#include <libidmap2/proto/fabricated_v1.pb.h>
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+#include "idmap2/ResourceContainer.h"
+#include "idmap2/Result.h"
+
+namespace android::idmap2 {
+
+struct FabricatedOverlay {
+  struct Builder {
+    Builder(const std::string& package_name, const std::string& name,
+            const std::string& target_package_name);
+
+    Builder& SetOverlayable(const std::string& name);
+
+    Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
+                              uint32_t data_value);
+
+    WARN_UNUSED Result<FabricatedOverlay> Build();
+
+   private:
+    struct Entry {
+      std::string resource_name;
+      DataType data_type;
+      DataValue data_value;
+    };
+
+    std::string package_name_;
+    std::string name_;
+    std::string target_package_name_;
+    std::string target_overlayable_;
+    std::vector<Entry> entries_;
+  };
+
+  Result<Unit> ToBinaryStream(std::ostream& stream) const;
+  static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
+
+ private:
+  struct SerializedData {
+    std::unique_ptr<uint8_t[]> data;
+    size_t data_size;
+    uint32_t crc;
+  };
+
+  Result<SerializedData*> InitializeData() const;
+  Result<uint32_t> GetCrc() const;
+
+  FabricatedOverlay(pb::FabricatedOverlay&& overlay, std::optional<uint32_t> crc_from_disk = {});
+
+  pb::FabricatedOverlay overlay_pb_;
+  std::optional<uint32_t> crc_from_disk_;
+  mutable std::optional<SerializedData> data_;
+
+  friend struct FabricatedOverlayContainer;
+};
+
+struct FabricatedOverlayContainer : public OverlayResourceContainer {
+  static Result<std::unique_ptr<FabricatedOverlayContainer>> FromPath(std::string path);
+  static std::unique_ptr<FabricatedOverlayContainer> FromOverlay(FabricatedOverlay&& overlay);
+
+  WARN_UNUSED OverlayManifestInfo GetManifestInfo() const;
+
+  // inherited from OverlayResourceContainer
+  WARN_UNUSED Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+  WARN_UNUSED Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+
+  // inherited from ResourceContainer
+  WARN_UNUSED Result<uint32_t> GetCrc() const override;
+  WARN_UNUSED const std::string& GetPath() const override;
+  WARN_UNUSED Result<std::string> GetResourceName(ResourceId id) const override;
+
+  ~FabricatedOverlayContainer() override;
+
+ private:
+  FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path);
+  FabricatedOverlay overlay_;
+  std::string path_;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_FABRICATEDOVERLAY_H
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index c4e0e1f..2588688 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,7 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 
+#include <random>
 #include <string>
 
 namespace android::idmap2::utils {
@@ -26,6 +27,8 @@
 
 bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
 
+std::string RandomStringForPath(size_t length);
+
 }  // namespace android::idmap2::utils
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 1b815c1..58aff42 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -23,8 +23,8 @@
  *                               debug_info
  * data                       := data_header target_entry* target_inline_entry* overlay_entry*
  *                               string_pool
- * data_header                := target_package_id overlay_package_id padding(2) target_entry_count
- *                               target_inline_entry_count overlay_entry_count string_pool_index
+ * data_header                := target_entry_count target_inline_entry_count overlay_entry_count
+ *                               string_pool_index
  * target_entry               := target_id overlay_id
  * target_inline_entry        := target_id Res_value::size padding(1) Res_value::type
  *                               Res_value::value
@@ -68,11 +68,10 @@
 #include <vector>
 
 #include "android-base/macros.h"
-#include "androidfw/ApkAssets.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
+#include "idmap2/ResourceContainer.h"
 #include "idmap2/ResourceMapping.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
@@ -85,9 +84,6 @@
 // current version of the idmap binary format; must be incremented when the format is changed
 static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
 
-// Retrieves a crc generated using all of the files within the zip that can affect idmap generation.
-Result<uint32_t> GetPackageCrc(const ZipFile& zip_info);
-
 class IdmapHeader {
  public:
   static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
@@ -135,9 +131,9 @@
   // Invariant: anytime the idmap data encoding is changed, the idmap version
   // field *must* be incremented. Because of this, we know that if the idmap
   // header is up-to-date the entire file is up-to-date.
-  Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
-                          const std::string& overlay_name, PolicyBitmask fulfilled_policies,
-                          bool enforce_overlayable) const;
+  Result<Unit> IsUpToDate(const TargetResourceContainer& target,
+                          const OverlayResourceContainer& overlay, const std::string& overlay_name,
+                          PolicyBitmask fulfilled_policies, bool enforce_overlayable) const;
 
   Result<Unit> IsUpToDate(const std::string& target_path, const std::string& overlay_path,
                           const std::string& overlay_name, uint32_t target_crc,
@@ -169,14 +165,6 @@
    public:
     static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
 
-    inline PackageId GetTargetPackageId() const {
-      return target_package_id_;
-    }
-
-    inline PackageId GetOverlayPackageId() const {
-      return overlay_package_id_;
-    }
-
     inline uint32_t GetTargetEntryCount() const {
       return target_entry_count;
     }
@@ -196,8 +184,6 @@
     void accept(Visitor* v) const;
 
    private:
-    PackageId target_package_id_;
-    PackageId overlay_package_id_;
     uint32_t target_entry_count;
     uint32_t target_entry_inline_count;
     uint32_t overlay_entry_count;
@@ -275,11 +261,10 @@
   // file is used; change this in the next version of idmap to use a named
   // package instead; also update FromApkAssets to take additional parameters:
   // the target and overlay package names
-  static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
-                                                            const ApkAssets& overlay_apk_assets,
-                                                            const std::string& overlay_name,
-                                                            const PolicyBitmask& fulfilled_policies,
-                                                            bool enforce_overlayable);
+  static Result<std::unique_ptr<const Idmap>> FromContainers(
+      const TargetResourceContainer& target, const OverlayResourceContainer& overlay,
+      const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
+      bool enforce_overlayable);
 
   const std::unique_ptr<const IdmapHeader>& GetHeader() const {
     return header_;
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 2b4c761..4464201 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -41,9 +41,8 @@
 
  private:
   std::ostream& stream_;
-  AssetManager2 target_am_;
-  AssetManager2 overlay_am_;
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
+  std::unique_ptr<TargetResourceContainer> target_;
+  std::unique_ptr<OverlayResourceContainer> overlay_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 4583516..ebd0d1e 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -49,10 +49,9 @@
   void pad(size_t padding);
 
   std::ostream& stream_;
-  std::vector<std::unique_ptr<const ApkAssets>> apk_assets_;
-  AssetManager2 target_am_;
-  AssetManager2 overlay_am_;
   size_t offset_;
+  std::unique_ptr<TargetResourceContainer> target_;
+  std::unique_ptr<OverlayResourceContainer> overlay_;
 };
 
 }  // namespace idmap2
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
new file mode 100644
index 0000000..74a6f56
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "idmap2/Policies.h"
+#include "idmap2/ResourceUtils.h"
+
+namespace android::idmap2 {
+
+struct ResourceContainer {
+  WARN_UNUSED virtual Result<uint32_t> GetCrc() const = 0;
+  WARN_UNUSED virtual const std::string& GetPath() const = 0;
+  WARN_UNUSED virtual Result<std::string> GetResourceName(ResourceId id) const = 0;
+
+  virtual ~ResourceContainer() = default;
+};
+
+struct TargetResourceContainer : public ResourceContainer {
+  static Result<std::unique_ptr<TargetResourceContainer>> FromPath(std::string path);
+
+  WARN_UNUSED virtual Result<bool> DefinesOverlayable() const = 0;
+  WARN_UNUSED virtual Result<const android::OverlayableInfo*> GetOverlayableInfo(
+      ResourceId id) const = 0;
+  WARN_UNUSED virtual Result<ResourceId> GetResourceId(const std::string& name) const = 0;
+
+  ~TargetResourceContainer() override = default;
+};
+
+struct OverlayManifestInfo {
+  std::string package_name;     // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string name;             // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_package;   // NOLINT(misc-non-private-member-variables-in-classes)
+  std::string target_name;      // NOLINT(misc-non-private-member-variables-in-classes)
+  ResourceId resource_mapping;  // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+struct OverlayData {
+  struct ResourceIdValue {
+    // The overlay resource id.
+    ResourceId overlay_id;
+
+    // Whether or not references to the overlay resource id should be rewritten to its corresponding
+    // target id during resource resolution.
+    bool rewrite_id;
+  };
+
+  struct Value {
+    std::string resource_name;
+    std::variant<ResourceIdValue, TargetValue> value;
+  };
+
+  struct InlineStringPoolData {
+    // The binary data of the android::ResStringPool string pool.
+    std::unique_ptr<uint8_t[]> data;
+
+    // The length of the binary data.
+    uint32_t data_length;
+
+    // The offset added to TargetValue#data_value (the index of the string in the inline string
+    // pool) in order to prevent the indices of the overlay resource table string pool from
+    // colliding with the inline string pool indices.
+    uint32_t string_pool_offset;
+  };
+
+  // The overlay's mapping of target resource name to overlaid value. Use a vector to enforce that
+  // the overlay pairs are inserted into the ResourceMapping in the specified ordered.
+  std::vector<Value> pairs;
+
+  // If the overlay maps a target resource to a string literal (not a string resource), then the
+  // this field contains information about the string pool in which the string literal resides so it
+  // can be inlined into an idmap.
+  std::optional<InlineStringPoolData> string_pool_data;
+};
+
+struct OverlayResourceContainer : public ResourceContainer {
+  static Result<std::unique_ptr<OverlayResourceContainer>> FromPath(std::string path);
+
+  WARN_UNUSED virtual Result<OverlayManifestInfo> FindOverlayInfo(
+      const std::string& name) const = 0;
+  WARN_UNUSED virtual Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const = 0;
+
+  ~OverlayResourceContainer() override = default;
+};
+
+}  // namespace android::idmap2
+
+#endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCECONTAINER_H
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index f66916c..5a0a384 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -17,30 +17,22 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
 #define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
 
+#include <androidfw/ApkAssets.h>
+
 #include <map>
 #include <memory>
 #include <utility>
 
-#include "androidfw/ApkAssets.h"
+#include "idmap2/FabricatedOverlay.h"
 #include "idmap2/LogInfo.h"
 #include "idmap2/Policies.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/XmlParser.h"
 
-using android::idmap2::utils::OverlayManifestInfo;
-
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 
 namespace android::idmap2 {
-
-struct TargetValue {
-  typedef uint8_t DataType;
-  typedef uint32_t DataValue;
-  DataType data_type;
-  DataValue data_value;
-};
-
 using TargetResourceMap = std::map<ResourceId, std::variant<ResourceId, TargetValue>>;
 using OverlayResourceMap = std::map<ResourceId, ResourceId>;
 
@@ -49,94 +41,60 @@
   // Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
   // `false` disables all overlayable and policy enforcement: this is intended for backwards
   // compatibility pre-Q and unit tests.
-  static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
-                                               const ApkAssets& overlay_apk_assets,
-                                               const OverlayManifestInfo& overlay_info,
-                                               const PolicyBitmask& fulfilled_policies,
-                                               bool enforce_overlayable, LogInfo& log_info);
+  static Result<ResourceMapping> FromContainers(const TargetResourceContainer& target,
+                                                const OverlayResourceContainer& overlay,
+                                                const OverlayManifestInfo& overlay_info,
+                                                const PolicyBitmask& fulfilled_policies,
+                                                bool enforce_overlayable, LogInfo& log_info);
 
   // Retrieves the mapping of target resource id to overlay value.
-  inline const TargetResourceMap& GetTargetToOverlayMap() const {
-    return target_map_;
-  }
+  WARN_UNUSED const TargetResourceMap& GetTargetToOverlayMap() const;
 
   // Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
   // an overlay resource to appear as a reference to its corresponding target resource at runtime.
-  OverlayResourceMap GetOverlayToTargetMap() const;
-
-  // Retrieves the build-time package id of the target package.
-  inline uint32_t GetTargetPackageId() const {
-    return target_package_id_;
-  }
-
-  // Retrieves the build-time package id of the overlay package.
-  inline uint32_t GetOverlayPackageId() const {
-    return overlay_package_id_;
-  }
+  WARN_UNUSED const OverlayResourceMap& GetOverlayToTargetMap() const;
 
   // Retrieves the offset that was added to the index of inline string overlay values so the indices
   // do not collide with the indices of the overlay resource table string pool.
-  inline uint32_t GetStringPoolOffset() const {
-    return string_pool_offset_;
-  }
+  WARN_UNUSED uint32_t GetStringPoolOffset() const;
 
   // Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
-  inline const StringPiece GetStringPoolData() const {
-    return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
-                       string_pool_data_length_);
-  }
+  WARN_UNUSED StringPiece GetStringPoolData() const;
 
  private:
   ResourceMapping() = default;
 
-  // Maps a target resource id to an overlay resource id.
-  // If rewrite_overlay_reference is `true` then references to the overlay
-  // resource should appear as a reference to its corresponding target resource at runtime.
-  Result<Unit> AddMapping(ResourceId target_resource, ResourceId overlay_resource,
-                          bool rewrite_overlay_reference);
-
-  // Maps a target resource id to a data type and value combination.
-  // The `data_type` is the runtime format of the data value (see Res_value::dataType).
-  Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
-                          TargetValue::DataValue data_value);
-
-  // Removes the overlay value mapping for the target resource.
-  void RemoveMapping(ResourceId target_resource);
-
-  // Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
-  static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
-                                                       const LoadedPackage* target_package,
-                                                       const LoadedPackage* overlay_package,
-                                                       size_t string_pool_offset,
-                                                       const XmlParser& overlay_parser,
-                                                       LogInfo& log_info);
-
-  // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
-  // a target resource, a resource must exist in the overlay with the same type and entry name as
-  // the target resource.
-  static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
-                                                             const AssetManager2* overlay_am,
-                                                             const LoadedPackage* target_package,
-                                                             const LoadedPackage* overlay_package,
-                                                             LogInfo& log_info);
-
-  // Removes resources that do not pass policy or overlayable checks of the target package.
-  void FilterOverlayableResources(const AssetManager2* target_am,
-                                  const LoadedPackage* target_package,
-                                  const LoadedPackage* overlay_package,
-                                  const OverlayManifestInfo& overlay_info,
-                                  const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
+  // Maps a target resource id to an overlay resource id or a android::Res_value value.
+  //
+  // If `allow_rewriting_` is true, then the overlay-to-target map will be populated if the target
+  // resource id is mapped to an overlay resource id.
+  Result<Unit> AddMapping(ResourceId target_resource,
+                          const std::variant<OverlayData::ResourceIdValue, TargetValue>& value);
 
   TargetResourceMap target_map_;
-  std::multimap<ResourceId, ResourceId> overlay_map_;
-
-  uint32_t target_package_id_ = 0;
-  uint32_t overlay_package_id_ = 0;
+  OverlayResourceMap overlay_map_;
   uint32_t string_pool_offset_ = 0;
   uint32_t string_pool_data_length_ = 0;
   std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
 };
 
+inline const TargetResourceMap& ResourceMapping::GetTargetToOverlayMap() const {
+  return target_map_;
+}
+
+inline const OverlayResourceMap& ResourceMapping::GetOverlayToTargetMap() const {
+  return overlay_map_;
+}
+
+inline uint32_t ResourceMapping::GetStringPoolOffset() const {
+  return string_pool_offset_;
+}
+
+inline StringPiece ResourceMapping::GetStringPoolData() const {
+  return StringPiece(reinterpret_cast<const char*>(string_pool_data_.get()),
+                     string_pool_data_length_);
+}
+
 }  // namespace android::idmap2
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index cd14d3e..a0202df 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -22,19 +22,26 @@
 
 #include "androidfw/AssetManager2.h"
 #include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
-// use typedefs to let the compiler warn us about implicit casts
-typedef uint32_t ResourceId;  // 0xpptteeee
-typedef uint8_t PackageId;    // pp in 0xpptteeee
-typedef uint8_t TypeId;       // tt in 0xpptteeee
-typedef uint16_t EntryId;     // eeee in 0xpptteeee
-
 #define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
 #define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
 
+// use typedefs to let the compiler warn us about implicit casts
+using ResourceId = uint32_t;  // 0xpptteeee
+using PackageId = uint8_t;    // pp in 0xpptteeee
+using TypeId = uint8_t;       // tt in 0xpptteeee
+using EntryId = uint16_t;     // eeee in 0xpptteeee
+
+using DataType = uint8_t;    // Res_value::dataType
+using DataValue = uint32_t;  // Res_value::data
+
+struct TargetValue {
+  DataType data_type;
+  DataValue data_value;
+};
+
 namespace utils {
 
 // Returns whether the Res_value::data_type represents a dynamic or regular resource reference.
@@ -43,20 +50,10 @@
 // Converts the Res_value::data_type to a human-readable string representation.
 StringPiece DataTypeToString(uint8_t data_type);
 
-struct OverlayManifestInfo {
-  std::string name;            // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_package;  // NOLINT(misc-non-private-member-variables-in-classes)
-  std::string target_name;     // NOLINT(misc-non-private-member-variables-in-classes)
-  uint32_t resource_mapping;   // NOLINT(misc-non-private-member-variables-in-classes)
-};
-
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       const std::string& name);
-
+// Retrieves the type and entry name of the resource in the AssetManager in the form type/entry.
 Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
 
 }  // namespace utils
-
 }  // namespace android::idmap2
 
 #endif  // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
index 1c74ab3..c968a5e 100644
--- a/cmds/idmap2/include/idmap2/XmlParser.h
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -30,8 +30,7 @@
 
 namespace android::idmap2 {
 
-class XmlParser {
- public:
+struct XmlParser {
   using Event = ResXMLParser::event_code_t;
   class iterator;
 
@@ -127,23 +126,19 @@
   };
 
   // Creates a new xml parser beginning at the first tag.
-  static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
-                                                         bool copy_data = false);
-  ~XmlParser();
+  static Result<XmlParser> Create(const void* data, size_t size, bool copy_data = false);
 
   inline iterator tree_iterator() const {
-    return iterator(tree_);
+    return iterator(*tree_);
   }
 
   inline const ResStringPool& get_strings() const {
-    return tree_.getStrings();
+    return tree_->getStrings();
   }
 
  private:
-  XmlParser() = default;
-  mutable ResXMLTree tree_;
-
-  DISALLOW_COPY_AND_ASSIGN(XmlParser);
+  explicit XmlParser(std::unique_ptr<ResXMLTree> tree);
+  mutable std::unique_ptr<ResXMLTree> tree_;
 };
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h
deleted file mode 100644
index 8f50e36..0000000
--- a/cmds/idmap2/include/idmap2/ZipFile.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
-
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "idmap2/Result.h"
-#include "ziparchive/zip_archive.h"
-
-namespace android::idmap2 {
-
-struct MemoryChunk {
-  size_t size;
-  uint8_t buf[0];
-
-  static std::unique_ptr<MemoryChunk> Allocate(size_t size);
-
- private:
-  MemoryChunk() {
-  }
-};
-
-class ZipFile {
- public:
-  static std::unique_ptr<const ZipFile> Open(const std::string& path);
-
-  std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
-  Result<uint32_t> Crc(const std::string& entryPath) const;
-
-  ~ZipFile();
-
- private:
-  explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
-  }
-
-  const ::ZipArchiveHandle handle_;
-
-  DISALLOW_COPY_AND_ASSIGN(ZipFile);
-};
-
-}  // namespace android::idmap2
-
-#endif  // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index c163107..3bbe9d9 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -87,10 +87,6 @@
 }
 
 void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
-  Write8(header.GetTargetPackageId());
-  Write8(header.GetOverlayPackageId());
-  Write8(0U);  // padding
-  Write8(0U);  // padding
   Write32(header.GetTargetEntryCount());
   Write32(header.GetTargetInlineEntryCount());
   Write32(header.GetOverlayEntryCount());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
new file mode 100644
index 0000000..4f61801
--- /dev/null
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ */
+
+#include "idmap2/FabricatedOverlay.h"
+
+#include <androidfw/ResourceUtils.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <utils/ByteOrder.h>
+#include <zlib.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+namespace {
+bool Read32(std::istream& stream, uint32_t* out) {
+  uint32_t value;
+  if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
+    *out = dtohl(value);
+    return true;
+  }
+  return false;
+}
+
+void Write32(std::ostream& stream, uint32_t value) {
+  uint32_t x = htodl(value);
+  stream.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
+}
+}  // namespace
+
+FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+                                     std::optional<uint32_t> crc_from_disk)
+    : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+}
+
+FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
+                                    const std::string& target_package_name) {
+  package_name_ = package_name;
+  name_ = name;
+  target_package_name_ = target_package_name;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetOverlayable(const std::string& name) {
+  target_overlayable_ = name;
+  return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+    const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
+  entries_.emplace_back(Entry{resource_name, data_type, data_value});
+  return *this;
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
+  std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+  for (const auto& res_entry : entries_) {
+    StringPiece package_substr;
+    StringPiece type_name;
+    StringPiece entry_name;
+    if (!android::ExtractResourceName(StringPiece(res_entry.resource_name), &package_substr,
+                                      &type_name, &entry_name)) {
+      return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
+    }
+
+    std::string package_name =
+        package_substr.empty() ? target_package_name_ : package_substr.to_string();
+    if (type_name.empty()) {
+      return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
+    }
+
+    if (entry_name.empty()) {
+      return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
+    }
+
+    auto package = entries.find(package_name);
+    if (package == entries.end()) {
+      package = entries
+                    .insert(std::make_pair<>(
+                        package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+                    .first;
+    }
+
+    auto type = package->second.find(type_name.to_string());
+    if (type == package->second.end()) {
+      type =
+          package->second
+              .insert(std::make_pair<>(type_name.to_string(), std::map<std::string, TargetValue>()))
+              .first;
+    }
+
+    auto entry = type->second.find(entry_name.to_string());
+    if (entry == type->second.end()) {
+      entry = type->second.insert(std::make_pair<>(entry_name.to_string(), TargetValue())).first;
+    }
+
+    entry->second = TargetValue{res_entry.data_type, res_entry.data_value};
+  }
+
+  pb::FabricatedOverlay overlay_pb;
+  overlay_pb.set_package_name(package_name_);
+  overlay_pb.set_name(name_);
+  overlay_pb.set_target_package_name(target_package_name_);
+  overlay_pb.set_target_overlayable(target_overlayable_);
+
+  for (const auto& package : entries) {
+    auto package_pb = overlay_pb.add_packages();
+    package_pb->set_name(package.first);
+
+    for (const auto& type : package.second) {
+      auto type_pb = package_pb->add_types();
+      type_pb->set_name(type.first);
+
+      for (const auto& entry : type.second) {
+        auto entry_pb = type_pb->add_entries();
+        entry_pb->set_name(entry.first);
+        pb::ResourceValue* value = entry_pb->mutable_res_value();
+        value->set_data_type(entry.second.data_type);
+        value->set_data_value(entry.second.data_value);
+      }
+    }
+  }
+
+  return FabricatedOverlay(std::move(overlay_pb));
+}
+
+Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
+  uint32_t magic;
+  if (!Read32(stream, &magic)) {
+    return Error("Failed to read fabricated overlay magic.");
+  }
+
+  if (magic != kFabricatedOverlayMagic) {
+    return Error("Not a fabricated overlay file.");
+  }
+
+  uint32_t version;
+  if (!Read32(stream, &version)) {
+    return Error("Failed to read fabricated overlay version.");
+  }
+
+  if (version != 1) {
+    return Error("Invalid fabricated overlay version '%u'.", version);
+  }
+
+  uint32_t crc;
+  if (!Read32(stream, &crc)) {
+    return Error("Failed to read fabricated overlay version.");
+  }
+
+  pb::FabricatedOverlay overlay{};
+  if (!overlay.ParseFromIstream(&stream)) {
+    return Error("Failed read fabricated overlay proto.");
+  }
+
+  // If the proto version is the latest version, then the contents of the proto must be the same
+  // when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
+  // proto to the latest version will likely change the contents of the fabricated overlay.
+  return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+                                                   ? std::optional<uint32_t>(crc)
+                                                   : std::nullopt);
+}
+
+Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
+  if (!data_.has_value()) {
+    auto size = overlay_pb_.ByteSizeLong();
+    auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+
+    // Ensure serialization is deterministic
+    google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+    google::protobuf::io::CodedOutputStream output_stream(&array_stream);
+    output_stream.SetSerializationDeterministic(true);
+    overlay_pb_.SerializeWithCachedSizes(&output_stream);
+    if (output_stream.HadError() || size != output_stream.ByteCount()) {
+      return Error("Failed to serialize fabricated overlay.");
+    }
+
+    // Calculate the crc using the proto data and the version.
+    uint32_t crc = crc32(0L, Z_NULL, 0);
+    crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+                sizeof(uint32_t));
+    crc = crc32(crc, data.get(), size);
+    data_ = SerializedData{std::move(data), size, crc};
+  }
+  return &(*data_);
+}
+Result<uint32_t> FabricatedOverlay::GetCrc() const {
+  if (crc_from_disk_.has_value()) {
+    return *crc_from_disk_;
+  }
+  auto data = InitializeData();
+  if (!data) {
+    return data.GetError();
+  }
+  return (*data)->crc;
+}
+
+Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
+  auto data = InitializeData();
+  if (!data) {
+    return data.GetError();
+  }
+
+  Write32(stream, kFabricatedOverlayMagic);
+  Write32(stream, kFabricatedOverlayCurrentVersion);
+  Write32(stream, (*data)->crc);
+  stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+  if (stream.bad()) {
+    return Error("Failed to write serialized fabricated overlay.");
+  }
+
+  return Unit{};
+}
+
+using FabContainer = FabricatedOverlayContainer;
+FabContainer::FabricatedOverlayContainer(FabricatedOverlay&& overlay, std::string&& path)
+    : overlay_(std::forward<FabricatedOverlay>(overlay)), path_(std::forward<std::string>(path)) {
+}
+
+FabContainer::~FabricatedOverlayContainer() = default;
+
+Result<std::unique_ptr<FabContainer>> FabContainer::FromPath(std::string path) {
+  std::fstream fin(path);
+  auto overlay = FabricatedOverlay::FromBinaryStream(fin);
+  if (!overlay) {
+    return overlay.GetError();
+  }
+  return std::unique_ptr<FabContainer>(
+      new FabricatedOverlayContainer(std::move(*overlay), std::move(path)));
+}
+
+std::unique_ptr<FabricatedOverlayContainer> FabContainer::FromOverlay(FabricatedOverlay&& overlay) {
+  return std::unique_ptr<FabContainer>(
+      new FabricatedOverlayContainer(std::move(overlay), {} /* path */));
+}
+
+OverlayManifestInfo FabContainer::GetManifestInfo() const {
+  const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+  return OverlayManifestInfo{
+      .package_name = overlay_pb.package_name(),
+      .name = overlay_pb.name(),
+      .target_package = overlay_pb.target_package_name(),
+      .target_name = overlay_pb.target_overlayable(),
+  };
+}
+
+Result<OverlayManifestInfo> FabContainer::FindOverlayInfo(const std::string& name) const {
+  const OverlayManifestInfo info = GetManifestInfo();
+  if (name != info.name) {
+    return Error("Failed to find name '%s' in fabricated overlay", name.c_str());
+  }
+  return info;
+}
+
+Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+  const pb::FabricatedOverlay& overlay_pb = overlay_.overlay_pb_;
+  if (info.name != overlay_pb.name()) {
+    return Error("Failed to find name '%s' in fabricated overlay", info.name.c_str());
+  }
+
+  OverlayData result{};
+  for (const auto& package : overlay_pb.packages()) {
+    for (const auto& type : package.types()) {
+      for (const auto& entry : type.entries()) {
+        auto name = base::StringPrintf("%s:%s/%s", package.name().c_str(), type.name().c_str(),
+                                       entry.name().c_str());
+        const auto& res_value = entry.res_value();
+        result.pairs.emplace_back(OverlayData::Value{
+            name, TargetValue{.data_type = static_cast<uint8_t>(res_value.data_type()),
+                              .data_value = res_value.data_value()}});
+      }
+    }
+  }
+  return result;
+}
+
+Result<uint32_t> FabContainer::GetCrc() const {
+  return overlay_.GetCrc();
+}
+
+const std::string& FabContainer::GetPath() const {
+  return path_;
+}
+
+Result<std::string> FabContainer::GetResourceName(ResourceId /* id */) const {
+  return Error("Fabricated overlay does not contain resources.");
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 3af1f70..98a4cea 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -47,4 +47,19 @@
 }
 #endif
 
+std::string RandomStringForPath(const size_t length) {
+  constexpr char kChars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  constexpr size_t kCharLastIndex = sizeof(kChars) - 1;
+
+  std::string out_rand;
+  out_rand.reserve(length);
+
+  std::random_device rd;
+  std::uniform_int_distribution<int> dist(0, kCharLastIndex);
+  for (size_t i = 0; i < length; i++) {
+    out_rand[i] = kChars[dist(rd) % (kCharLastIndex)];
+  }
+  return out_rand;
+}
+
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index a0cc407..6515d55 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -20,23 +20,16 @@
 #include <iostream>
 #include <iterator>
 #include <limits>
-#include <map>
 #include <memory>
-#include <set>
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "android-base/macros.h"
-#include "android-base/stringprintf.h"
 #include "androidfw/AssetManager2.h"
 #include "idmap2/ResourceMapping.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 #include "idmap2/SysTrace.h"
-#include "idmap2/ZipFile.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
 
 namespace android::idmap2 {
 
@@ -93,22 +86,13 @@
 
 }  // namespace
 
-Result<uint32_t> GetPackageCrc(const ZipFile& zip) {
-  const Result<uint32_t> a = zip.Crc("resources.arsc");
-  const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
-  return a && b
-             ? Result<uint32_t>(*a ^ *b)
-             : Error("failed to get CRC for \"%s\"", a ? "AndroidManifest.xml" : "resources.arsc");
-}
-
 std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
   if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_)) {
     return nullptr;
   }
 
-  if (idmap_header->magic_ != kIdmapMagic ||
-      idmap_header->version_ != kIdmapCurrentVersion) {
+  if (idmap_header->magic_ != kIdmapMagic || idmap_header->version_ != kIdmapCurrentVersion) {
     // Do not continue parsing if the file is not a current version idmap.
     return nullptr;
   }
@@ -127,32 +111,22 @@
   return std::move(idmap_header);
 }
 
-Result<Unit> IdmapHeader::IsUpToDate(const std::string& target_path,
-                                     const std::string& overlay_path,
+Result<Unit> IdmapHeader::IsUpToDate(const TargetResourceContainer& target,
+                                     const OverlayResourceContainer& overlay,
                                      const std::string& overlay_name,
                                      PolicyBitmask fulfilled_policies,
                                      bool enforce_overlayable) const {
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path);
-  if (!target_zip) {
-    return Error("failed to open target %s", target_path.c_str());
-  }
-
-  const Result<uint32_t> target_crc = GetPackageCrc(*target_zip);
+  const Result<uint32_t> target_crc = target.GetCrc();
   if (!target_crc) {
     return Error("failed to get target crc");
   }
 
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path);
-  if (!overlay_zip) {
-    return Error("failed to overlay target %s", overlay_path.c_str());
-  }
-
-  const Result<uint32_t> overlay_crc = GetPackageCrc(*overlay_zip);
+  const Result<uint32_t> overlay_crc = overlay.GetCrc();
   if (!overlay_crc) {
     return Error("failed to get overlay crc");
   }
 
-  return IsUpToDate(target_path, overlay_path, overlay_name, *target_crc, *overlay_crc,
+  return IsUpToDate(target.GetPath(), overlay.GetPath(), overlay_name, *target_crc, *overlay_crc,
                     fulfilled_policies, enforce_overlayable);
 }
 
@@ -209,11 +183,7 @@
 
 std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
   std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
-
-  uint8_t padding;
-  if (!Read8(stream, &idmap_data_header->target_package_id_) ||
-      !Read8(stream, &idmap_data_header->overlay_package_id_) || !Read8(stream, &padding) ||
-      !Read8(stream, &padding) || !Read32(stream, &idmap_data_header->target_entry_count) ||
+  if (!Read32(stream, &idmap_data_header->target_entry_count) ||
       !Read32(stream, &idmap_data_header->target_entry_inline_count) ||
       !Read32(stream, &idmap_data_header->overlay_entry_count) ||
       !Read32(stream, &idmap_data_header->string_pool_index_offset)) {
@@ -321,8 +291,6 @@
   }
 
   std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
-  data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
-  data_header->overlay_package_id_ = resource_mapping.GetOverlayPackageId();
   data_header->target_entry_count = static_cast<uint32_t>(data->target_entries_.size());
   data_header->target_entry_inline_count =
       static_cast<uint32_t>(data->target_inline_entries_.size());
@@ -332,57 +300,46 @@
   return {std::move(data)};
 }
 
-Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
-                                                          const ApkAssets& overlay_apk_assets,
-                                                          const std::string& overlay_name,
-                                                          const PolicyBitmask& fulfilled_policies,
-                                                          bool enforce_overlayable) {
+Result<std::unique_ptr<const Idmap>> Idmap::FromContainers(const TargetResourceContainer& target,
+                                                           const OverlayResourceContainer& overlay,
+                                                           const std::string& overlay_name,
+                                                           const PolicyBitmask& fulfilled_policies,
+                                                           bool enforce_overlayable) {
   SYSTRACE << "Idmap::FromApkAssets";
-  const std::string& target_apk_path = target_apk_assets.GetPath();
-  const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
-
-  const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
-  if (!target_zip) {
-    return Error("failed to open target as zip");
-  }
-
-  const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
-  if (!overlay_zip) {
-    return Error("failed to open overlay as zip");
-  }
-
   std::unique_ptr<IdmapHeader> header(new IdmapHeader());
   header->magic_ = kIdmapMagic;
   header->version_ = kIdmapCurrentVersion;
 
-  Result<uint32_t> crc = GetPackageCrc(*target_zip);
-  if (!crc) {
-    return Error(crc.GetError(), "failed to get zip CRC for target");
+  const auto target_crc = target.GetCrc();
+  if (!target_crc) {
+    return Error(target_crc.GetError(), "failed to get zip CRC for '%s'", target.GetPath().data());
   }
-  header->target_crc_ = *crc;
+  header->target_crc_ = *target_crc;
 
-  crc = GetPackageCrc(*overlay_zip);
-  if (!crc) {
-    return Error(crc.GetError(), "failed to get zip CRC for overlay");
+  const auto overlay_crc = overlay.GetCrc();
+  if (!overlay_crc) {
+    return Error(overlay_crc.GetError(), "failed to get zip CRC for '%s'",
+                 overlay.GetPath().data());
   }
-  header->overlay_crc_ = *crc;
+  header->overlay_crc_ = *overlay_crc;
+
   header->fulfilled_policies_ = fulfilled_policies;
   header->enforce_overlayable_ = enforce_overlayable;
-  header->target_path_ = target_apk_path;
-  header->overlay_path_ = overlay_apk_path;
+  header->target_path_ = target.GetPath();
+  header->overlay_path_ = overlay.GetPath();
   header->overlay_name_ = overlay_name;
 
-  auto info = utils::ExtractOverlayManifestInfo(overlay_apk_path, overlay_name);
+  auto info = overlay.FindOverlayInfo(overlay_name);
   if (!info) {
-    return info.GetError();
+    return Error(info.GetError(), "failed to get overlay info for '%s'", overlay.GetPath().data());
   }
 
   LogInfo log_info;
-  auto resource_mapping =
-      ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *info,
-                                     fulfilled_policies, enforce_overlayable, log_info);
+  auto resource_mapping = ResourceMapping::FromContainers(
+      target, overlay, *info, fulfilled_policies, enforce_overlayable, log_info);
   if (!resource_mapping) {
-    return resource_mapping.GetError();
+    return Error(resource_mapping.GetError(), "failed to generate resource map for '%s'",
+                 overlay.GetPath().data());
   }
 
   auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index 7e090a9..721612c 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -27,8 +27,6 @@
 
 namespace android::idmap2 {
 
-#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
-
 #define TAB "    "
 
 void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
@@ -36,8 +34,8 @@
 
 void PrettyPrintVisitor::visit(const IdmapHeader& header) {
   stream_ << "Paths:" << std::endl
-          << TAB "target apk path  : " << header.GetTargetPath() << std::endl
-          << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
+          << TAB "target path  : " << header.GetTargetPath() << std::endl
+          << TAB "overlay path : " << header.GetOverlayPath() << std::endl;
 
   if (!header.GetOverlayName().empty()) {
     stream_ << "Overlay name: " << header.GetOverlayName() << std::endl;
@@ -53,14 +51,11 @@
     }
   }
 
-  if (auto target_apk_ = ApkAssets::Load(header.GetTargetPath())) {
-    target_am_.SetApkAssets({target_apk_.get()});
-    apk_assets_.push_back(std::move(target_apk_));
+  if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+    target_ = std::move(*target);
   }
-
-  if (auto overlay_apk = ApkAssets::Load(header.GetOverlayPath())) {
-    overlay_am_.SetApkAssets({overlay_apk.get()});
-    apk_assets_.push_back(std::move(overlay_apk));
+  if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+    overlay_ = std::move(*overlay);
   }
 
   stream_ << "Mapping:" << std::endl;
@@ -72,23 +67,20 @@
 void PrettyPrintVisitor::visit(const IdmapData& data) {
   static constexpr const char* kUnknownResourceName = "???";
 
-  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
-  const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
   const ResStringPool string_pool(data.GetStringPoolData().data(), data.GetStringPoolData().size());
   const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
 
   for (const auto& target_entry : data.GetTargetEntries()) {
     std::string target_name = kUnknownResourceName;
-    if (target_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+    if (target_ != nullptr) {
+      if (auto name = target_->GetResourceName(target_entry.target_id)) {
         target_name = *name;
       }
     }
 
     std::string overlay_name = kUnknownResourceName;
-    if (overlay_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id)) {
+    if (overlay_ != nullptr) {
+      if (auto name = overlay_->GetResourceName(target_entry.overlay_id)) {
         overlay_name = *name;
       }
     }
@@ -112,8 +104,8 @@
     }
 
     std::string target_name = kUnknownResourceName;
-    if (target_package_loaded) {
-      if (auto name = utils::ResToTypeEntryName(target_am_, target_entry.target_id)) {
+    if (target_ != nullptr) {
+      if (auto name = target_->GetResourceName(target_entry.target_id)) {
         target_name = *name;
       }
     }
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index b517aa3..a016a36 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -18,16 +18,13 @@
 
 #include <algorithm>
 #include <cstdarg>
-#include <string>
 
 #include "android-base/macros.h"
 #include "android-base/stringprintf.h"
-#include "androidfw/ApkAssets.h"
 #include "idmap2/PolicyUtils.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
-using android::ApkAssets;
 using android::idmap2::policy::PoliciesToDebugString;
 
 namespace android::idmap2 {
@@ -48,27 +45,19 @@
   print(header.GetOverlayName(), true /* print_value */, "overlay name");
   print(header.GetDebugInfo(), false /* print_value */, "debug info");
 
-  auto target_apk_ = ApkAssets::Load(header.GetTargetPath());
-  if (target_apk_) {
-    target_am_.SetApkAssets({target_apk_.get()});
-    apk_assets_.push_back(std::move(target_apk_));
+  if (auto target = TargetResourceContainer::FromPath(header.GetTargetPath())) {
+    target_ = std::move(*target);
   }
-
-  auto overlay_apk_ = ApkAssets::Load(header.GetOverlayPath());
-  if (overlay_apk_) {
-    overlay_am_.SetApkAssets({overlay_apk_.get()});
-    apk_assets_.push_back(std::move(overlay_apk_));
+  if (auto overlay = OverlayResourceContainer::FromPath(header.GetOverlayPath())) {
+    overlay_ = std::move(*overlay);
   }
 }
 
 void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
-  const bool target_package_loaded = !target_am_.GetApkAssets().empty();
-  const bool overlay_package_loaded = !overlay_am_.GetApkAssets().empty();
-
   for (auto& target_entry : data.GetTargetEntries()) {
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(target_entry.target_id);
     }
     if (target_name) {
       print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -77,8 +66,8 @@
     }
 
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.overlay_id);
+    if (overlay_ != nullptr) {
+      overlay_name = overlay_->GetResourceName(target_entry.overlay_id);
     }
     if (overlay_name) {
       print(target_entry.overlay_id, "overlay id: %s", overlay_name->c_str());
@@ -89,8 +78,8 @@
 
   for (auto& target_entry : data.GetTargetInlineEntries()) {
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, target_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(target_entry.target_id);
     }
     if (target_name) {
       print(target_entry.target_id, "target id: %s", target_name->c_str());
@@ -104,10 +93,10 @@
           utils::DataTypeToString(target_entry.value.data_type).data());
 
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded &&
+    if (overlay_ != nullptr &&
         (target_entry.value.data_value == Res_value::TYPE_REFERENCE ||
          target_entry.value.data_value == Res_value::TYPE_DYNAMIC_REFERENCE)) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, target_entry.value.data_value);
+      overlay_name = overlay_->GetResourceName(target_entry.value.data_value);
     }
 
     if (overlay_name) {
@@ -119,8 +108,8 @@
 
   for (auto& overlay_entry : data.GetOverlayEntries()) {
     Result<std::string> overlay_name(Error(""));
-    if (overlay_package_loaded) {
-      overlay_name = utils::ResToTypeEntryName(overlay_am_, overlay_entry.overlay_id);
+    if (overlay_ != nullptr) {
+      overlay_name = overlay_->GetResourceName(overlay_entry.overlay_id);
     }
 
     if (overlay_name) {
@@ -130,8 +119,8 @@
     }
 
     Result<std::string> target_name(Error(""));
-    if (target_package_loaded) {
-      target_name = utils::ResToTypeEntryName(target_am_, overlay_entry.target_id);
+    if (target_ != nullptr) {
+      target_name = target_->GetResourceName(overlay_entry.target_id);
     }
 
     if (target_name) {
@@ -145,9 +134,6 @@
 }
 
 void RawPrintVisitor::visit(const IdmapData::Header& header) {
-  print(header.GetTargetPackageId(), "target package id");
-  print(header.GetOverlayPackageId(), "overlay package id");
-  align();
   print(header.GetTargetEntryCount(), "target entry count");
   print(header.GetTargetInlineEntryCount(), "target inline entry count");
   print(header.GetOverlayEntryCount(), "overlay entry count");
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
new file mode 100644
index 0000000..9147cca
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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.
+ */
+
+#include "idmap2/ResourceContainer.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/Util.h"
+#include "idmap2/FabricatedOverlay.h"
+#include "idmap2/XmlParser.h"
+
+namespace android::idmap2 {
+namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
+
+#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
+
+constexpr ResourceId kAttrName = 0x01010003;
+constexpr ResourceId kAttrResourcesMap = 0x01010609;
+constexpr ResourceId kAttrTargetName = 0x0101044d;
+constexpr ResourceId kAttrTargetPackage = 0x01010021;
+
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+const LoadedPackage* GetPackageAtIndex0(const LoadedArsc* loaded_arsc) {
+  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
+  if (packages.empty()) {
+    return nullptr;
+  }
+  return loaded_arsc->GetPackageById(packages[0]->GetPackageId());
+}
+
+Result<uint32_t> CalculateCrc(const ZipAssetsProvider* zip_assets) {
+  constexpr const char* kResourcesArsc = "resources.arsc";
+  std::optional<uint32_t> res_crc = zip_assets->GetCrc(kResourcesArsc);
+  if (!res_crc) {
+    return Error("failed to get CRC for '%s'", kResourcesArsc);
+  }
+
+  constexpr const char* kManifest = "AndroidManifest.xml";
+  std::optional<uint32_t> man_crc = zip_assets->GetCrc(kManifest);
+  if (!man_crc) {
+    return Error("failed to get CRC for '%s'", kManifest);
+  }
+
+  return *res_crc ^ *man_crc;
+}
+
+Result<XmlParser> OpenXmlParser(const std::string& entry_path, const ZipAssetsProvider* zip) {
+  auto manifest = zip->Open(entry_path);
+  if (manifest == nullptr) {
+    return Error("failed to find %s ", entry_path.c_str());
+  }
+
+  auto size = manifest->getLength();
+  auto buffer = manifest->getIncFsBuffer(true /* aligned */).convert<uint8_t>();
+  if (!buffer.verify(size)) {
+    return Error("failed to read entire %s", entry_path.c_str());
+  }
+
+  return XmlParser::Create(buffer.unsafe_ptr(), size, true /* copyData */);
+}
+
+Result<XmlParser> OpenXmlParser(ResourceId id, const ZipAssetsProvider* zip,
+                                const AssetManager2* am) {
+  const auto ref_table = am->GetDynamicRefTableForCookie(0);
+  if (ref_table == nullptr) {
+    return Error("failed to find dynamic ref table for cookie 0");
+  }
+
+  ref_table->lookupResourceId(&id);
+  auto value = am->GetResource(id);
+  if (!value.has_value()) {
+    return Error("failed to find resource for id 0x%08x", id);
+  }
+
+  if (value->type != Res_value::TYPE_STRING) {
+    return Error("resource for is 0x%08x is not a file", id);
+  }
+
+  auto string_pool = am->GetStringPoolForCookie(value->cookie);
+  auto file = string_pool->string8ObjectAt(value->data);
+  if (!file.has_value()) {
+    return Error("failed to find string for index %d", value->data);
+  }
+
+  return OpenXmlParser(file->c_str(), zip);
+}
+
+Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const ZipAssetsProvider* zip,
+                                                       const std::string& name) {
+  Result<XmlParser> xml = OpenXmlParser("AndroidManifest.xml", zip);
+  if (!xml) {
+    return xml.GetError();
+  }
+
+  auto manifest_it = xml->tree_iterator();
+  if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
+    return Error("root element tag is not <manifest> in AndroidManifest.xml");
+  }
+
+  std::string package_name;
+  if (auto result_str = manifest_it->GetAttributeStringValue("package")) {
+    package_name = *result_str;
+  } else {
+    return result_str.GetError();
+  }
+
+  for (auto&& it : manifest_it) {
+    if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
+      continue;
+    }
+
+    OverlayManifestInfo info{};
+    info.package_name = package_name;
+    if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
+      if (*result_str != name) {
+        // A value for android:name was found, but either a the name does not match the requested
+        // name, or an <overlay> tag with no name was requested.
+        continue;
+      }
+      info.name = *result_str;
+    } else if (!name.empty()) {
+      // This tag does not have a value for android:name, but an <overlay> tag with a specific name
+      // has been requested.
+      continue;
+    }
+
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
+      info.target_package = *result_str;
+    } else {
+      return Error("android:targetPackage missing from <overlay> in AndroidManifest.xml");
+    }
+
+    if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
+      info.target_name = *result_str;
+    }
+
+    if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
+      if (utils::IsReference((*result_value).dataType)) {
+        info.resource_mapping = (*result_value).data;
+      } else {
+        return Error("android:resourcesMap is not a reference in AndroidManifest.xml");
+      }
+    }
+    return info;
+  }
+
+  return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml", name.c_str());
+}
+
+Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider* zip,
+                                          const AssetManager2* overlay_am,
+                                          const LoadedArsc* overlay_arsc,
+                                          const LoadedPackage* overlay_package) {
+  auto parser = OpenXmlParser(id, zip, overlay_am);
+  if (!parser) {
+    return parser.GetError();
+  }
+
+  OverlayData overlay_data{};
+  const uint32_t string_pool_offset = overlay_arsc->GetStringPool()->size();
+  const uint8_t package_id = overlay_package->GetPackageId();
+  auto root_it = parser->tree_iterator();
+  if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
+    return Error("root element is not <overlay> tag");
+  }
+
+  auto overlay_it_end = root_it.end();
+  for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
+    if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
+      return Error("failed to parse overlay xml document");
+    }
+
+    if (overlay_it->event() != XmlParser::Event::START_TAG) {
+      continue;
+    }
+
+    if (overlay_it->name() != "item") {
+      return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+    }
+
+    Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
+    if (!target_resource) {
+      return Error(R"(<item> tag missing expected attribute "target")");
+    }
+
+    Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
+    if (!overlay_resource) {
+      return Error(R"(<item> tag missing expected attribute "value")");
+    }
+
+    if (overlay_resource->dataType == Res_value::TYPE_STRING) {
+      overlay_resource->data += string_pool_offset;
+    }
+
+    if (utils::IsReference(overlay_resource->dataType)) {
+      // Only rewrite resources defined within the overlay package to their corresponding target
+      // resource ids at runtime.
+      bool rewrite_id = package_id == EXTRACT_PACKAGE(overlay_resource->data);
+      overlay_data.pairs.emplace_back(OverlayData::Value{
+          *target_resource, OverlayData::ResourceIdValue{overlay_resource->data, rewrite_id}});
+    } else {
+      overlay_data.pairs.emplace_back(
+          OverlayData::Value{*target_resource, TargetValue{.data_type = overlay_resource->dataType,
+                                                           .data_value = overlay_resource->data}});
+    }
+  }
+
+  const auto& string_pool = parser->get_strings();
+  const uint32_t string_pool_data_length = string_pool.bytes();
+  overlay_data.string_pool_data = OverlayData::InlineStringPoolData{
+      .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+      .data_length = string_pool_data_length,
+      .string_pool_offset = string_pool_offset,
+  };
+
+  // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
+  memcpy(overlay_data.string_pool_data->data.get(), string_pool.data().unsafe_ptr(),
+         string_pool_data_length);
+  return overlay_data;
+}
+
+OverlayData CreateResourceMappingLegacy(const AssetManager2* overlay_am,
+                                        const LoadedPackage* overlay_package) {
+  OverlayData overlay_data{};
+  for (const ResourceId overlay_resid : *overlay_package) {
+    if (auto name = utils::ResToTypeEntryName(*overlay_am, overlay_resid)) {
+      // Disable rewriting. Overlays did not support internal references before
+      // android:resourcesMap. Do not introduce new behavior.
+      overlay_data.pairs.emplace_back(OverlayData::Value{
+          *name, OverlayData::ResourceIdValue{overlay_resid, false /* rewrite_id */}});
+    }
+  }
+  return overlay_data;
+}
+
+struct ResState {
+  std::unique_ptr<ApkAssets> apk_assets;
+  const LoadedArsc* arsc;
+  const LoadedPackage* package;
+  std::unique_ptr<AssetManager2> am;
+  ZipAssetsProvider* zip_assets;
+
+  static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+    ResState state;
+    state.zip_assets = zip.get();
+    if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+      return Error("failed to load apk asset");
+    }
+
+    if ((state.arsc = state.apk_assets->GetLoadedArsc()) == nullptr) {
+      return Error("failed to retrieve loaded arsc");
+    }
+
+    if ((state.package = GetPackageAtIndex0(state.arsc)) == nullptr) {
+      return Error("failed to retrieve loaded package at index 0");
+    }
+
+    state.am = std::make_unique<AssetManager2>();
+    if (!state.am->SetApkAssets({state.apk_assets.get()})) {
+      return Error("failed to create asset manager");
+    }
+
+    return state;
+  }
+};
+
+}  // namespace
+
+struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
+  static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+
+  // inherited from TargetResourceContainer
+  Result<bool> DefinesOverlayable() const override;
+  Result<const android::OverlayableInfo*> GetOverlayableInfo(ResourceId id) const override;
+  Result<ResourceId> GetResourceId(const std::string& name) const override;
+
+  // inherited from OverlayResourceContainer
+  Result<OverlayData> GetOverlayData(const OverlayManifestInfo& info) const override;
+  Result<OverlayManifestInfo> FindOverlayInfo(const std::string& name) const override;
+
+  // inherited from ResourceContainer
+  Result<uint32_t> GetCrc() const override;
+  Result<std::string> GetResourceName(ResourceId id) const override;
+  const std::string& GetPath() const override;
+
+  ~ApkResourceContainer() override = default;
+
+ private:
+  ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets, std::string path);
+
+  Result<const ResState*> GetState() const;
+  ZipAssetsProvider* GetZipAssets() const;
+
+  mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
+  std::string path_;
+};
+
+ApkResourceContainer::ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zip_assets,
+                                           std::string path)
+    : state_(std::move(zip_assets)), path_(std::move(path)) {
+}
+
+Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
+    const std::string& path) {
+  auto zip_assets = ZipAssetsProvider::Create(path);
+  if (zip_assets == nullptr) {
+    return Error("failed to load zip assets");
+  }
+  return std::unique_ptr<ApkResourceContainer>(
+      new ApkResourceContainer(std::move(zip_assets), path));
+}
+
+Result<const ResState*> ApkResourceContainer::GetState() const {
+  if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
+    return state;
+  }
+
+  auto state =
+      ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+  if (!state) {
+    return state.GetError();
+  }
+
+  state_ = std::move(*state);
+  return &std::get<ResState>(state_);
+}
+
+ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+  if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
+    return zip->get();
+  }
+  return std::get<ResState>(state_).zip_assets;
+}
+
+Result<bool> ApkResourceContainer::DefinesOverlayable() const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return (*state)->package->DefinesOverlayable();
+}
+
+Result<const android::OverlayableInfo*> ApkResourceContainer::GetOverlayableInfo(
+    ResourceId id) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return (*state)->package->GetOverlayableInfo(id);
+}
+
+Result<OverlayManifestInfo> ApkResourceContainer::FindOverlayInfo(const std::string& name) const {
+  return ExtractOverlayManifestInfo(GetZipAssets(), name);
+}
+
+Result<OverlayData> ApkResourceContainer::GetOverlayData(const OverlayManifestInfo& info) const {
+  const auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+
+  if (info.resource_mapping != 0) {
+    return CreateResourceMapping(info.resource_mapping, GetZipAssets(), (*state)->am.get(),
+                                 (*state)->arsc, (*state)->package);
+  }
+  return CreateResourceMappingLegacy((*state)->am.get(), (*state)->package);
+}
+
+Result<uint32_t> ApkResourceContainer::GetCrc() const {
+  return CalculateCrc(GetZipAssets());
+}
+
+const std::string& ApkResourceContainer::GetPath() const {
+  return path_;
+}
+
+Result<ResourceId> ApkResourceContainer::GetResourceId(const std::string& name) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  auto id = (*state)->am->GetResourceId(name, "", (*state)->package->GetPackageName());
+  if (!id.has_value()) {
+    return Error("failed to find resource '%s'", name.c_str());
+  }
+
+  // Retrieve the compile-time resource id of the target resource.
+  return REWRITE_PACKAGE(*id, (*state)->package->GetPackageId());
+}
+
+Result<std::string> ApkResourceContainer::GetResourceName(ResourceId id) const {
+  auto state = GetState();
+  if (!state) {
+    return state.GetError();
+  }
+  return utils::ResToTypeEntryName(*(*state)->am, id);
+}
+
+Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
+    std::string path) {
+  auto result = ApkResourceContainer::FromPath(path);
+  if (!result) {
+    return result.GetError();
+  }
+  return std::unique_ptr<TargetResourceContainer>(result->release());
+}
+
+Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::FromPath(
+    std::string path) {
+  // Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
+  if (android::IsFabricatedOverlay(path)) {
+    auto result = FabricatedOverlayContainer::FromPath(path);
+    if (!result) {
+      return result.GetError();
+    }
+    return std::unique_ptr<OverlayResourceContainer>(result->release());
+  }
+
+  // Fallback to loading the container as an APK.
+  auto result = ApkResourceContainer::FromPath(path);
+  if (!result) {
+    return result.GetError();
+  }
+  return std::unique_ptr<OverlayResourceContainer>(result->release());
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 46eeb8e..3bbbf24 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -30,19 +30,12 @@
 
 using android::base::StringPrintf;
 using android::idmap2::utils::BitmaskToPolicies;
-using android::idmap2::utils::IsReference;
-using android::idmap2::utils::ResToTypeEntryName;
 using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
 namespace {
-
-#define REWRITE_PACKAGE(resid, package_id) \
-  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
-#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
-
 std::string ConcatPolicies(const std::vector<std::string>& policies) {
   std::string message;
   for (const std::string& policy : policies) {
@@ -55,11 +48,11 @@
   return message;
 }
 
-Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
+Result<Unit> CheckOverlayable(const TargetResourceContainer& target,
                               const OverlayManifestInfo& overlay_info,
                               const PolicyBitmask& fulfilled_policies,
                               const ResourceId& target_resource) {
-  static constexpr const PolicyBitmask sDefaultPolicies =
+  constexpr const PolicyBitmask kDefaultPolicies =
       PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION |
       PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE |
       PolicyFlags::CONFIG_SIGNATURE;
@@ -68,8 +61,13 @@
   // the overlay is preinstalled, signed with the same signature as the target or signed with the
   // same signature as reference package defined in SystemConfig under 'overlay-config-signature'
   // tag.
-  if (!target_package.DefinesOverlayable()) {
-    return (sDefaultPolicies & fulfilled_policies) != 0
+  const Result<bool> defines_overlayable = target.DefinesOverlayable();
+  if (!defines_overlayable) {
+    return Error(defines_overlayable.GetError(), "unable to retrieve overlayable info");
+  }
+
+  if (!*defines_overlayable) {
+    return (kDefaultPolicies & fulfilled_policies) != 0
                ? Result<Unit>({})
                : Error(
                      "overlay must be preinstalled, signed with the same signature as the target,"
@@ -77,367 +75,112 @@
                      " <overlay-config-signature>.");
   }
 
-  const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
-  if (overlayable_info == nullptr) {
+  const auto overlayable_info = target.GetOverlayableInfo(target_resource);
+  if (!overlayable_info) {
+    return overlayable_info.GetError();
+  }
+
+  if (*overlayable_info == nullptr) {
     // Do not allow non-overlayable resources to be overlaid.
     return Error("target resource has no overlayable declaration");
   }
 
-  if (overlay_info.target_name != overlayable_info->name) {
+  if (overlay_info.target_name != (*overlayable_info)->name) {
     // If the overlay supplies a target overlayable name, the resource must belong to the
     // overlayable defined with the specified name to be overlaid.
     return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
-                 overlay_info.target_name.c_str(), overlayable_info->name.c_str());
+                 overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
   }
 
   // Enforce policy restrictions if the resource is declared as overlayable.
-  if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
+  if (((*overlayable_info)->policy_flags & fulfilled_policies) == 0) {
     return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
                  ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
-                 ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
+                 ConcatPolicies(BitmaskToPolicies((*overlayable_info)->policy_flags)).c_str());
   }
 
   return Result<Unit>({});
 }
 
-// TODO(martenkongstad): scan for package name instead of assuming package at index 0
-//
-// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
-// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
-// this assumption tends to work out. That said, the correct thing to do is to scan
-// resources.arsc for a package with a given name as read from the package manifest instead of
-// relying on a hard-coded index. This however requires storing the package name in the idmap
-// header, which in turn requires incrementing the idmap version. Because the initial version of
-// idmap2 is compatible with idmap, this will have to wait for now.
-const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
-  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
-  if (packages.empty()) {
-    return nullptr;
+std::string GetDebugResourceName(const ResourceContainer& container, ResourceId resid) {
+  auto name = container.GetResourceName(resid);
+  if (name) {
+    return *name;
   }
-  int id = packages[0]->GetPackageId();
-  return loaded_arsc.GetPackageById(id);
+  return StringPrintf("0x%08x", resid);
 }
-
-Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
-                                                        const AssetManager2& asset_manager) {
-  auto value = asset_manager.GetResource(resource_id);
-  if (!value.has_value()) {
-    return Error("failed to find resource for id 0x%08x", resource_id);
-  }
-
-  if (value->type != Res_value::TYPE_STRING) {
-    return Error("resource for is 0x%08x is not a file", resource_id);
-  }
-
-  auto string_pool = asset_manager.GetStringPoolForCookie(value->cookie);
-  auto file = string_pool->string8ObjectAt(value->data);
-  if (!file.has_value()) {
-    return Error("failed to find string for index %d", value->data);
-  }
-
-  // Load the overlay resource mappings from the file specified using android:resourcesMap.
-  auto asset = asset_manager.OpenNonAsset(file->c_str(), Asset::AccessMode::ACCESS_BUFFER);
-  if (asset == nullptr) {
-    return Error("file \"%s\" not found", file->c_str());
-  }
-
-  return asset;
-}
-
 }  // namespace
 
-Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
-                                                               const LoadedPackage* target_package,
-                                                               const LoadedPackage* overlay_package,
-                                                               size_t string_pool_offset,
-                                                               const XmlParser& overlay_parser,
-                                                               LogInfo& log_info) {
-  ResourceMapping resource_mapping;
-  auto root_it = overlay_parser.tree_iterator();
-  if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
-    return Error("root element is not <overlay> tag");
+Result<ResourceMapping> ResourceMapping::FromContainers(const TargetResourceContainer& target,
+                                                        const OverlayResourceContainer& overlay,
+                                                        const OverlayManifestInfo& overlay_info,
+                                                        const PolicyBitmask& fulfilled_policies,
+                                                        bool enforce_overlayable,
+                                                        LogInfo& log_info) {
+  auto overlay_data = overlay.GetOverlayData(overlay_info);
+  if (!overlay_data) {
+    return overlay_data.GetError();
   }
 
-  const uint8_t target_package_id = target_package->GetPackageId();
-  const uint8_t overlay_package_id = overlay_package->GetPackageId();
-  auto overlay_it_end = root_it.end();
-  for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
-    if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
-      return Error("failed to parse overlay xml document");
-    }
-
-    if (overlay_it->event() != XmlParser::Event::START_TAG) {
+  ResourceMapping mapping;
+  for (const auto& overlay_pair : overlay_data->pairs) {
+    const auto target_resid = target.GetResourceId(overlay_pair.resource_name);
+    if (!target_resid) {
+      log_info.Warning(LogMessage() << target_resid.GetErrorMessage());
       continue;
     }
 
-    if (overlay_it->name() != "item") {
-      return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+    if (enforce_overlayable) {
+      // Filter out resources the overlay is not allowed to override.
+      auto overlayable = CheckOverlayable(target, overlay_info, fulfilled_policies, *target_resid);
+      if (!overlayable) {
+        log_info.Warning(LogMessage() << "overlay '" << overlay.GetPath()
+                                      << "' is not allowed to overlay resource '"
+                                      << GetDebugResourceName(target, *target_resid)
+                                      << "' in target: " << overlayable.GetErrorMessage());
+        continue;
+      }
     }
 
-    Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
-    if (!target_resource) {
-      return Error(R"(<item> tag missing expected attribute "target")");
-    }
-
-    Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
-    if (!overlay_resource) {
-      return Error(R"(<item> tag missing expected attribute "value")");
-    }
-
-    auto target_id_result =
-        target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
-    if (!target_id_result.has_value()) {
-      log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
-                                    << "\" in target resources");
-      continue;
-    }
-
-    // Retrieve the compile-time resource id of the target resource.
-    uint32_t target_id = REWRITE_PACKAGE(*target_id_result, target_package_id);
-
-    if (overlay_resource->dataType == Res_value::TYPE_STRING) {
-      overlay_resource->data += string_pool_offset;
-    }
-
-    if (IsReference(overlay_resource->dataType)) {
-      // Only rewrite resources defined within the overlay package to their corresponding target
-      // resource ids at runtime.
-      bool rewrite_reference = overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data);
-      resource_mapping.AddMapping(target_id, overlay_resource->data, rewrite_reference);
-    } else {
-      resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data);
+    if (auto result = mapping.AddMapping(*target_resid, overlay_pair.value); !result) {
+      return Error(result.GetError(), "failed to add mapping for '%s'",
+                   GetDebugResourceName(target, *target_resid).c_str());
     }
   }
 
-  return resource_mapping;
+  auto& string_pool_data = overlay_data->string_pool_data;
+  if (string_pool_data.has_value()) {
+    mapping.string_pool_offset_ = string_pool_data->string_pool_offset;
+    mapping.string_pool_data_ = std::move(string_pool_data->data);
+    mapping.string_pool_data_length_ = string_pool_data->data_length;
+  }
+
+  return std::move(mapping);
 }
 
-Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
-    const AssetManager2* target_am, const AssetManager2* overlay_am,
-    const LoadedPackage* target_package, const LoadedPackage* overlay_package, LogInfo& log_info) {
-  ResourceMapping resource_mapping;
-  const uint8_t target_package_id = target_package->GetPackageId();
-  const auto end = overlay_package->end();
-  for (auto iter = overlay_package->begin(); iter != end; ++iter) {
-    const ResourceId overlay_resid = *iter;
-    Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
-    if (!name) {
-      continue;
+Result<Unit> ResourceMapping::AddMapping(
+    ResourceId target_resource,
+    const std::variant<OverlayData::ResourceIdValue, TargetValue>& value) {
+  if (target_map_.find(target_resource) != target_map_.end()) {
+    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+  }
+
+  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
+  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
+
+  if (auto overlay_resource = std::get_if<OverlayData::ResourceIdValue>(&value)) {
+    target_map_.insert(std::make_pair(target_resource, overlay_resource->overlay_id));
+    if (overlay_resource->rewrite_id) {
+      // An overlay resource can override multiple target resources at once. Rewrite the overlay
+      // resource as the first target resource it overrides.
+      overlay_map_.insert(std::make_pair(overlay_resource->overlay_id, target_resource));
     }
-
-    // Find the resource with the same type and entry name within the target package.
-    const std::string full_name =
-        base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
-    auto target_resource_result = target_am->GetResourceId(full_name);
-    if (!target_resource_result.has_value()) {
-      log_info.Warning(LogMessage()
-                       << "failed to find resource \"" << full_name << "\" in target resources");
-      continue;
-    }
-
-    // Retrieve the compile-time resource id of the target resource.
-    ResourceId target_resource = REWRITE_PACKAGE(*target_resource_result, target_package_id);
-    resource_mapping.AddMapping(target_resource, overlay_resid,
-                                false /* rewrite_overlay_reference */);
-  }
-
-  return resource_mapping;
-}
-
-void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
-                                                 const LoadedPackage* target_package,
-                                                 const LoadedPackage* overlay_package,
-                                                 const OverlayManifestInfo& overlay_info,
-                                                 const PolicyBitmask& fulfilled_policies,
-                                                 LogInfo& log_info) {
-  std::set<ResourceId> remove_ids;
-  for (const auto& target_map : target_map_) {
-    const ResourceId target_resid = target_map.first;
-    Result<Unit> success =
-        CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
-    if (success) {
-      continue;
-    }
-
-    // Attempting to overlay a resource that is not allowed to be overlaid is treated as a
-    // warning.
-    Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
-    if (!name) {
-      name = StringPrintf("0x%08x", target_resid);
-    }
-
-    log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
-                                  << "\" is not allowed to overlay resource \"" << *name
-                                  << "\" in target: " << success.GetErrorMessage());
-
-    remove_ids.insert(target_resid);
-  }
-
-  for (const ResourceId target_resid : remove_ids) {
-    RemoveMapping(target_resid);
-  }
-}
-
-Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
-                                                       const ApkAssets& overlay_apk_assets,
-                                                       const OverlayManifestInfo& overlay_info,
-                                                       const PolicyBitmask& fulfilled_policies,
-                                                       bool enforce_overlayable,
-                                                       LogInfo& log_info) {
-  AssetManager2 target_asset_manager;
-  if (!target_asset_manager.SetApkAssets({&target_apk_assets})) {
-    return Error("failed to create target asset manager");
-  }
-
-  AssetManager2 overlay_asset_manager;
-  if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets})) {
-    return Error("failed to create overlay asset manager");
-  }
-
-  const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
-  if (target_arsc == nullptr) {
-    return Error("failed to load target resources.arsc");
-  }
-
-  const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
-  if (overlay_arsc == nullptr) {
-    return Error("failed to load overlay resources.arsc");
-  }
-
-  const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
-  if (target_pkg == nullptr) {
-    return Error("failed to load target package from resources.arsc");
-  }
-
-  const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
-  if (overlay_pkg == nullptr) {
-    return Error("failed to load overlay package from resources.arsc");
-  }
-
-  size_t string_pool_data_length = 0U;
-  size_t string_pool_offset = 0U;
-  std::unique_ptr<uint8_t[]> string_pool_data;
-  Result<ResourceMapping> resource_mapping = {{}};
-  if (overlay_info.resource_mapping != 0U) {
-    // Use the dynamic reference table to find the assigned resource id of the map xml.
-    const auto& ref_table = overlay_asset_manager.GetDynamicRefTableForCookie(0);
-    uint32_t resource_mapping_id = overlay_info.resource_mapping;
-    ref_table->lookupResourceId(&resource_mapping_id);
-
-    // Load the overlay resource mappings from the file specified using android:resourcesMap.
-    auto asset = OpenNonAssetFromResource(resource_mapping_id, overlay_asset_manager);
-    if (!asset) {
-      return Error("failed opening xml for android:resourcesMap: %s",
-                   asset.GetErrorMessage().c_str());
-    }
-
-    auto parser =
-        XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
-    if (!parser) {
-      return Error("failed opening ResXMLTree");
-    }
-
-    // Copy the xml string pool data before the parse goes out of scope.
-    auto& string_pool = (*parser)->get_strings();
-    string_pool_data_length = string_pool.bytes();
-    string_pool_data.reset(new uint8_t[string_pool_data_length]);
-
-    // Overlays should not be incrementally installed, so calling unsafe_ptr is fine here.
-    memcpy(string_pool_data.get(), string_pool.data().unsafe_ptr(), string_pool_data_length);
-
-    // Offset string indices by the size of the overlay resource table string pool.
-    string_pool_offset = overlay_arsc->GetStringPool()->size();
-
-    resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
-                                             string_pool_offset, *(*parser), log_info);
   } else {
-    // If no file is specified using android:resourcesMap, it is assumed that the overlay only
-    // defines resources intended to override target resources of the same type and name.
-    resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
-                                                   target_pkg, overlay_pkg, log_info);
+    auto overlay_value = std::get<TargetValue>(value);
+    target_map_.insert(std::make_pair(target_resource, overlay_value));
   }
 
-  if (!resource_mapping) {
-    return resource_mapping.GetError();
-  }
-
-  if (enforce_overlayable) {
-    // Filter out resources the overlay is not allowed to override.
-    (*resource_mapping)
-        .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
-                                    fulfilled_policies, log_info);
-  }
-
-  resource_mapping->target_package_id_ = target_pkg->GetPackageId();
-  resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
-  resource_mapping->string_pool_offset_ = string_pool_offset;
-  resource_mapping->string_pool_data_ = std::move(string_pool_data);
-  resource_mapping->string_pool_data_length_ = string_pool_data_length;
-  return std::move(*resource_mapping);
-}
-
-OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
-  // An overlay resource can override multiple target resources at once. Rewrite the overlay
-  // resource as the first target resource it overrides.
-  OverlayResourceMap map;
-  for (const auto& mappings : overlay_map_) {
-    map.insert(std::make_pair(mappings.first, mappings.second));
-  }
-  return map;
-}
-
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource, ResourceId overlay_resource,
-                                         bool rewrite_overlay_reference) {
-  if (target_map_.find(target_resource) != target_map_.end()) {
-    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
-  }
-
-  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
-  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
-  target_map_.insert(std::make_pair(target_resource, overlay_resource));
-
-  if (rewrite_overlay_reference) {
-    overlay_map_.insert(std::make_pair(overlay_resource, target_resource));
-  }
   return Unit{};
 }
 
-Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
-                                         TargetValue::DataType data_type,
-                                         TargetValue::DataValue data_value) {
-  if (target_map_.find(target_resource) != target_map_.end()) {
-    return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
-  }
-
-  // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
-  // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
-
-  target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
-  return Unit{};
-}
-
-void ResourceMapping::RemoveMapping(ResourceId target_resource) {
-  auto target_iter = target_map_.find(target_resource);
-  if (target_iter == target_map_.end()) {
-    return;
-  }
-
-  const auto value = target_iter->second;
-  target_map_.erase(target_iter);
-
-  const ResourceId* overlay_resource = std::get_if<ResourceId>(&value);
-  if (overlay_resource == nullptr) {
-    return;
-  }
-
-  auto overlay_iter = overlay_map_.equal_range(*overlay_resource);
-  for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
-    if (i->second == target_resource) {
-      overlay_map_.erase(i);
-      return;
-    }
-  }
-}
-
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index 4e85e57..e809bf1 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -17,27 +17,16 @@
 #include "idmap2/ResourceUtils.h"
 
 #include <memory>
-#include <string>
 
 #include "androidfw/StringPiece.h"
 #include "androidfw/Util.h"
 #include "idmap2/Result.h"
-#include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 
 using android::StringPiece16;
 using android::idmap2::Result;
-using android::idmap2::XmlParser;
-using android::idmap2::ZipFile;
 using android::util::Utf16ToUtf8;
 
 namespace android::idmap2::utils {
-namespace {
-constexpr ResourceId kAttrName = 0x01010003;
-constexpr ResourceId kAttrResourcesMap = 0x01010609;
-constexpr ResourceId kAttrTargetName = 0x0101044d;
-constexpr ResourceId kAttrTargetPackage = 0x01010021;
-}  // namespace
 
 bool IsReference(uint8_t data_type) {
   return data_type == Res_value::TYPE_REFERENCE || data_type == Res_value::TYPE_DYNAMIC_REFERENCE;
@@ -97,71 +86,4 @@
   return out;
 }
 
-Result<OverlayManifestInfo> ExtractOverlayManifestInfo(const std::string& path,
-                                                       const std::string& name) {
-  std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
-  if (!zip) {
-    return Error("failed to open %s as a zip file", path.c_str());
-  }
-
-  std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
-  if (!entry) {
-    return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
-  }
-
-  Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
-  if (!xml) {
-    return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
-  }
-
-  auto manifest_it = (*xml)->tree_iterator();
-  if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
-    return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
-  }
-
-  for (auto&& it : manifest_it) {
-    if (it.event() != XmlParser::Event::START_TAG || it.name() != "overlay") {
-      continue;
-    }
-
-    OverlayManifestInfo info{};
-    if (auto result_str = it.GetAttributeStringValue(kAttrName, "android:name")) {
-      if (*result_str != name) {
-        // A value for android:name was found, but either a the name does not match the requested
-        // name, or an <overlay> tag with no name was requested.
-        continue;
-      }
-      info.name = *result_str;
-    } else if (!name.empty()) {
-      // This tag does not have a value for android:name, but an <overlay> tag with a specific name
-      // has been requested.
-      continue;
-    }
-
-    if (auto result_str = it.GetAttributeStringValue(kAttrTargetPackage, "android:targetPackage")) {
-      info.target_package = *result_str;
-    } else {
-      return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
-                   result_str.GetErrorMessage().c_str());
-    }
-
-    if (auto result_str = it.GetAttributeStringValue(kAttrTargetName, "android:targetName")) {
-      info.target_name = *result_str;
-    }
-
-    if (auto result_value = it.GetAttributeValue(kAttrResourcesMap, "android:resourcesMap")) {
-      if (IsReference((*result_value).dataType)) {
-        info.resource_mapping = (*result_value).data;
-      } else {
-        return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
-                     path.c_str());
-      }
-    }
-    return info;
-  }
-
-  return Error("<overlay> with android:name \"%s\" missing from AndroidManifest.xml of %s",
-               name.c_str(), path.c_str());
-}
-
 }  // namespace android::idmap2::utils
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 00baea4..70822c8 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -151,16 +151,18 @@
   return value ? GetStringValue(parser_, *value, name) : value.GetError();
 }
 
-Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
-                                                           bool copy_data) {
-  auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
-  if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
+XmlParser::XmlParser(std::unique_ptr<ResXMLTree> tree) : tree_(std::move(tree)) {
+}
+
+Result<XmlParser> XmlParser::Create(const void* data, size_t size, bool copy_data) {
+  auto tree = std::make_unique<ResXMLTree>();
+  if (tree->setTo(data, size, copy_data) != NO_ERROR) {
     return Error("Malformed xml block");
   }
 
   // Find the beginning of the first tag.
   XmlParser::Event event;
-  while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+  while ((event = tree->next()) != XmlParser::Event::BAD_DOCUMENT &&
          event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
   }
 
@@ -172,11 +174,7 @@
     return Error("Bad xml document");
   }
 
-  return parser;
-}
-
-XmlParser::~XmlParser() {
-  tree_.uninit();
+  return XmlParser{std::move(tree)};
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
deleted file mode 100644
index 1e1a218..0000000
--- a/cmds/idmap2/libidmap2/ZipFile.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#include "idmap2/ZipFile.h"
-
-#include <memory>
-#include <string>
-
-#include "idmap2/Result.h"
-
-namespace android::idmap2 {
-
-std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
-  void* ptr = ::operator new(sizeof(MemoryChunk) + size);
-  std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
-  chunk->size = size;
-  return chunk;
-}
-
-std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
-  ::ZipArchiveHandle handle;
-  int32_t status = ::OpenArchive(path.c_str(), &handle);
-  if (status != 0) {
-    ::CloseArchive(handle);
-    return nullptr;
-  }
-  return std::unique_ptr<ZipFile>(new ZipFile(handle));
-}
-
-ZipFile::~ZipFile() {
-  ::CloseArchive(handle_);
-}
-
-std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
-  ::ZipEntry entry;
-  int32_t status = ::FindEntry(handle_, entryPath, &entry);
-  if (status != 0) {
-    return nullptr;
-  }
-  std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
-  status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
-  if (status != 0) {
-    return nullptr;
-  }
-  return chunk;
-}
-
-Result<uint32_t> ZipFile::Crc(const std::string& entryPath) const {
-  ::ZipEntry entry;
-  int32_t status = ::FindEntry(handle_, entryPath, &entry);
-  if (status != 0) {
-    return Error("failed to find zip entry %s", entryPath.c_str());
-  }
-  return entry.crc32;
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/proto/fabricated_v1.proto b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
new file mode 100644
index 0000000..a392b2b
--- /dev/null
+++ b/cmds/idmap2/libidmap2/proto/fabricated_v1.proto
@@ -0,0 +1,56 @@
+/*
+ * 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 = "proto3";
+
+package android.idmap2.pb;
+
+option optimize_for = LITE_RUNTIME;
+
+// All changes to the proto messages in this file MUST be backwards compatible. Backwards
+// incompatible changes will cause previously fabricated overlays to be considered corrupt by the
+// new proto message specification.
+message FabricatedOverlay {
+  repeated ResourcePackage packages = 1;
+  string name = 2;
+  string package_name = 3;
+  string target_package_name = 4;
+  string target_overlayable = 5;
+}
+
+message ResourcePackage {
+  string name = 1;
+  repeated ResourceType types = 2;
+}
+
+message ResourceType {
+  string name = 1;
+  repeated ResourceEntry entries = 2;
+}
+
+message ResourceEntry {
+  string name = 1;
+  oneof value {
+    ResourceValue res_value = 2;
+  }
+}
+
+message ResourceValue {
+  // Corresponds with android::Res_value::dataType
+  uint32 data_type = 1;
+  // Corresponds with android::Res_value::data
+  uint32 data_value = 2;
+}
\ No newline at end of file
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index 524aabc..bf63327 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -33,7 +33,7 @@
 namespace android::idmap2 {
 
 TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   auto result1 = Idmap::FromBinaryStream(raw_stream);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
new file mode 100644
index 0000000..79ab243
--- /dev/null
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -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.
+ */
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <idmap2/FabricatedOverlay.h>
+
+#include <fstream>
+
+namespace android::idmap2 {
+
+TEST(FabricatedOverlayTests, OverlayInfo) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetOverlayable("TestResources")
+          .Build();
+
+  ASSERT_TRUE(overlay);
+  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+  auto info = container->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info);
+  EXPECT_EQ("SandTheme", (*info).name);
+  EXPECT_EQ("TestResources", (*info).target_name);
+
+  info = container->FindOverlayInfo("OceanTheme");
+  ASSERT_FALSE(info);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValue) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+          .SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
+          .SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
+          .Build();
+  ASSERT_TRUE(overlay);
+  auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
+  auto info = container->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info);
+  ASSERT_TRUE((*info).target_name.empty());
+
+  auto crc = (*container).GetCrc();
+  ASSERT_TRUE(crc) << crc.GetErrorMessage();
+  EXPECT_NE(0U, *crc);
+
+  auto pairs = container->GetOverlayData(*info);
+  ASSERT_TRUE(pairs);
+  EXPECT_FALSE(pairs->string_pool_data.has_value());
+  ASSERT_EQ(3U, pairs->pairs.size());
+
+  auto& it = pairs->pairs[0];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  auto entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(1U, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+
+  it = pairs->pairs[1];
+  ASSERT_EQ("com.example.target:string/int3", it.resource_name);
+  entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(0x7f010000, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
+
+  it = pairs->pairs[2];
+  ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
+  entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  ASSERT_EQ(2U, entry->data_value);
+  ASSERT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+TEST(FabricatedOverlayTests, SetResourceValueBadArgs) {
+  {
+    auto builder =
+        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+            .SetResourceValue("int1", Res_value::TYPE_INT_DEC, 1U);
+    ASSERT_FALSE(builder.Build());
+  }
+  {
+    auto builder =
+        FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+            .SetResourceValue("com.example.target:int2", Res_value::TYPE_INT_DEC, 1U);
+    ASSERT_FALSE(builder.Build());
+  }
+}
+
+TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
+  auto overlay =
+      FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
+          .SetOverlayable("TestResources")
+          .SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+          .Build();
+  ASSERT_TRUE(overlay);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*overlay).ToBinaryStream(out));
+  out.close();
+
+  auto container = OverlayResourceContainer::FromPath(tf.path);
+  ASSERT_TRUE(container) << container.GetErrorMessage();
+  EXPECT_EQ(tf.path, (*container)->GetPath());
+
+  auto crc = (*container)->GetCrc();
+  ASSERT_TRUE(crc) << crc.GetErrorMessage();
+  EXPECT_NE(0U, *crc);
+
+  auto info = (*container)->FindOverlayInfo("SandTheme");
+  ASSERT_TRUE(info) << info.GetErrorMessage();
+  EXPECT_EQ("SandTheme", (*info).name);
+  EXPECT_EQ("TestResources", (*info).target_name);
+
+  auto pairs = (*container)->GetOverlayData(*info);
+  ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
+  EXPECT_EQ(1U, pairs->pairs.size());
+
+  auto& it = pairs->pairs[0];
+  ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
+  auto entry = std::get_if<TargetValue>(&it.value);
+  ASSERT_NE(nullptr, entry);
+  EXPECT_EQ(1U, entry->data_value);
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+}
+
+}  // namespace android::idmap2
\ No newline at end of file
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 16b68f0..9516ff8 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/file.h>
+
 #include <cstdio>  // fclose
 #include <fstream>
 #include <memory>
@@ -61,12 +63,12 @@
 }
 
 TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
   std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
   ASSERT_THAT(header, NotNull());
   ASSERT_EQ(header->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(header->GetVersion(), 0x07U);
+  ASSERT_EQ(header->GetVersion(), 0x08U);
   ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
   ASSERT_EQ(header->GetFulfilledPolicies(), 0x11);
@@ -81,7 +83,7 @@
   std::stringstream stream;
   stream << android::kIdmapMagic;
   stream << 0xffffffffU;
-  stream << std::string(kJunkSize, (char) 0xffU);
+  stream << std::string(kJunkSize, (char)0xffU);
   ASSERT_FALSE(Idmap::FromBinaryStream(stream));
 }
 
@@ -90,14 +92,13 @@
   std::stringstream stream;
   stream << 0xffffffffU;
   stream << android::kIdmapCurrentVersion;
-  stream << std::string(kJunkSize, (char) 0xffU);
+  stream << std::string(kJunkSize, (char)0xffU);
   ASSERT_FALSE(Idmap::FromBinaryStream(stream));
 }
 
 TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
   const size_t offset = kIdmapRawDataOffset;
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  kIdmapRawDataLen - offset);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
@@ -108,8 +109,7 @@
 
 TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
   const size_t offset = kIdmapRawDataOffset;
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
-                  kIdmapRawDataLen - offset);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData + offset), kIdmapRawDataLen - offset);
   std::istringstream stream(raw);
 
   std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
@@ -134,7 +134,7 @@
 }
 
 TEST(IdmapTests, CreateIdmapFromBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   auto result = Idmap::FromBinaryStream(stream);
@@ -143,7 +143,7 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
   ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), kIdmapRawDataPolicies);
@@ -177,7 +177,7 @@
 }
 
 TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData),
                   10);  // data too small
   std::istringstream stream(raw);
 
@@ -189,14 +189,14 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(
-      *target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_ALL_POLICIES, PolicyFlags::PUBLIC,
       /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
@@ -204,7 +204,7 @@
 
   ASSERT_THAT(idmap->GetHeader(), NotNull());
   ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
-  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x07U);
+  ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x08U);
   ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC);
   ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC);
@@ -218,15 +218,15 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+      /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
@@ -255,25 +255,66 @@
   ASSERT_OVERLAY_ENTRY(overlay_entries[3], R::overlay::string::str4, R::target::string::str4);
 }
 
+TEST(IdmapTests, FabricatedOverlay) {
+  std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
+
+  auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+                  .SetOverlayable("TestResources")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+                  .Build();
+
+  ASSERT_TRUE(frro);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*frro).ToBinaryStream(out));
+  out.close();
+
+  auto overlay = OverlayResourceContainer::FromPath(tf.path);
+  ASSERT_TRUE(overlay);
+
+  auto idmap_result = Idmap::FromContainers(**target, **overlay, "SandTheme", PolicyFlags::PUBLIC,
+                                            /* enforce_overlayable */ true);
+  ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
+  auto& idmap = *idmap_result;
+  ASSERT_THAT(idmap, NotNull());
+
+  const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+  ASSERT_EQ(dataBlocks.size(), 1U);
+
+  const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+  ASSERT_THAT(data, NotNull());
+  ASSERT_EQ(data->GetTargetEntries().size(), 0U);
+  ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
+
+  const auto& target_inline_entries = data->GetTargetInlineEntries();
+  ASSERT_EQ(target_inline_entries.size(), 2U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
+                             Res_value::TYPE_INT_DEC, 2U);
+  ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
+                             Res_value::TYPE_REFERENCE, 0x7f010000);
+}
+
 TEST(IdmapTests, FailCreateIdmapInvalidName) {
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
   {
-    auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk, "", PolicyFlags::PUBLIC,
-                                             /* enforce_overlayable */ true);
+    auto idmap_result = Idmap::FromContainers(**target, **overlay, "", PolicyFlags::PUBLIC,
+                                              /* enforce_overlayable */ true);
     ASSERT_FALSE(idmap_result);
   }
   {
-    auto idmap_result =
-        Idmap::FromApkAssets(*target_apk, *overlay_apk, "unknown", PolicyFlags::PUBLIC,
-                             /* enforce_overlayable */ true);
+    auto idmap_result = Idmap::FromContainers(**target, **overlay, "unknown", PolicyFlags::PUBLIC,
+                                              /* enforce_overlayable */ true);
     ASSERT_FALSE(idmap_result);
   }
 }
@@ -282,15 +323,15 @@
   std::string target_apk_path = GetTestDataPath() + "/target/target.apk";
   std::string overlay_apk_path = GetTestDataPath() + "/overlay/overlay-shared.apk";
 
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  auto idmap_result = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                           TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                           /* enforce_overlayable */ true);
+  auto idmap_result = Idmap::FromContainers(
+      **target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
+      /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap_result) << idmap_result.GetErrorMessage();
   auto& idmap = *idmap_result;
   ASSERT_THAT(idmap, NotNull());
@@ -328,30 +369,29 @@
 }
 
 Result<std::unique_ptr<const IdmapData>> TestIdmapDataFromApkAssets(
-    const std::string& local_target_apk_path, const std::string& local_overlay_apk_path,
+    const std::string& local_target_path, const std::string& local_overlay_path,
     const std::string& overlay_name, const PolicyBitmask& fulfilled_policies,
     bool enforce_overlayable) {
-  auto overlay_info =
-      utils::ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  const std::string target_path(GetTestDataPath() + local_target_path);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error(R"(Failed to load target "%s")", target_path.c_str());
+  }
+
+  const std::string overlay_path(GetTestDataPath() + local_overlay_path);
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error(R"(Failed to load overlay "%s")", overlay_path.c_str());
+  }
+
+  auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
   if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
-  }
-
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+    return Error(R"(Failed to find overlay name "%s")", overlay_name.c_str());
   }
 
   LogInfo log_info;
-  auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
-                                                fulfilled_policies, enforce_overlayable, log_info);
+  auto mapping = ResourceMapping::FromContainers(**target, **overlay, *overlay_info,
+                                                 fulfilled_policies, enforce_overlayable, log_info);
   if (!mapping) {
     return mapping.GetError();
   }
@@ -360,11 +400,9 @@
 }
 
 TEST(IdmapTests, CreateIdmapDataDoNotRewriteNonOverlayResourceId) {
-  auto idmap_data =
-      TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk", "DifferentPackages",
-
-                                 PolicyFlags::PUBLIC,
-                                 /* enforce_overlayable */ false);
+  auto idmap_data = TestIdmapDataFromApkAssets("/target/target.apk", "/overlay/overlay.apk",
+                                               "DifferentPackages", PolicyFlags::PUBLIC,
+                                               /* enforce_overlayable */ false);
 
   ASSERT_TRUE(idmap_data) << idmap_data.GetErrorMessage();
   auto& data = *idmap_data;
@@ -417,7 +455,7 @@
   const uint32_t target_crc = kIdmapRawDataTargetCrc;
   const uint32_t overlay_crc = kIdmapRawOverlayCrc;
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   auto result = Idmap::FromBinaryStream(raw_stream);
@@ -468,8 +506,8 @@
   ASSERT_THAT(bad_target_crc_header, NotNull());
   ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
   ASSERT_FALSE(bad_target_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                 target_crc, overlay_crc, policies,
+                                                 /* enforce_overlayable */ true));
 
   // overlay crc: bytes (0xc, 0xf)
   std::string bad_overlay_crc_string(stream.str());
@@ -483,8 +521,8 @@
   ASSERT_THAT(bad_overlay_crc_header, NotNull());
   ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
   ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                  target_crc, overlay_crc, policies,
+                                                  /* enforce_overlayable */ true));
 
   // fulfilled policy: bytes (0x10, 0x13)
   std::string bad_policy_string(stream.str());
@@ -522,8 +560,8 @@
   ASSERT_THAT(bad_target_path_header, NotNull());
   ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
   ASSERT_FALSE(bad_target_path_header->IsUpToDate(target_apk_path, overlay_apk_path, overlay_name,
-                                            target_crc, overlay_crc, policies,
-                                            /* enforce_overlayable */ true));
+                                                  target_crc, overlay_crc, policies,
+                                                  /* enforce_overlayable */ true));
 
   // overlay path: bytes (0x2c, 0x37)
   std::string bad_overlay_path_string(stream.str());
@@ -576,7 +614,7 @@
 };
 
 TEST(IdmapTests, TestVisitor) {
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(stream);
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index 87ce0f1..3d3d82a 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -27,35 +27,31 @@
 #include "idmap2/Idmap.h"
 #include "idmap2/PrettyPrintVisitor.h"
 
-using android::ApkAssets;
 using android::base::StringPrintf;
-using ::testing::NotNull;
 
-using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
   const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
   const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk,
-                                          TestConstants::OVERLAY_NAME_DEFAULT, PolicyFlags::PUBLIC,
-                                          /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
   PrettyPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
-  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("target path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
   ASSERT_NE(stream.str().find(StringPrintf("0x%08x -> 0x%08x (integer/int1 -> integer/int1)\n",
                                            R::target::integer::int1, R::overlay::integer::int1)),
             std::string::npos);
@@ -64,7 +60,7 @@
 TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -74,8 +70,8 @@
   PrettyPrintVisitor visitor(stream);
   (*idmap)->accept(&visitor);
 
-  ASSERT_NE(stream.str().find("target apk path  : "), std::string::npos);
-  ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+  ASSERT_NE(stream.str().find("target path  : "), std::string::npos);
+  ASSERT_NE(stream.str().find("overlay path : "), std::string::npos);
   ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000 (\?\?\? -> \?\?\?)\n"), std::string::npos);
 }
 
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 88f85ef..a6371cb 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -30,17 +30,16 @@
 #include "idmap2/RawPrintVisitor.h"
 
 using android::base::StringPrintf;
-using ::testing::NotNull;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
 namespace android::idmap2 {
 
-#define ASSERT_CONTAINS_REGEX(pattern, str)                       \
-  do {                                                            \
-    ASSERT_TRUE(std::regex_search(str, std::regex(pattern)))      \
-        << "pattern '" << pattern << "' not found in\n--------\n" \
-        << str << "--------";                                     \
+#define ASSERT_CONTAINS_REGEX(pattern, str)                         \
+  do {                                                              \
+    ASSERT_TRUE(std::regex_search(str, std::regex(pattern)))        \
+        << "pattern '" << (pattern) << "' not found in\n--------\n" \
+        << (str) << "--------";                                     \
   } while (0)
 
 #define ADDRESS "[0-9a-f]{8}: "
@@ -49,16 +48,15 @@
   fclose(stderr);  // silence expected warnings
 
   const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  ASSERT_THAT(target_apk, NotNull());
+  auto target = TargetResourceContainer::FromPath(target_apk_path);
+  ASSERT_TRUE(target);
 
   const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  ASSERT_THAT(overlay_apk, NotNull());
+  auto overlay = OverlayResourceContainer::FromPath(overlay_apk_path);
+  ASSERT_TRUE(overlay);
 
-  const auto idmap =
-      Idmap::FromApkAssets(*target_apk, *overlay_apk, TestConstants::OVERLAY_NAME_DEFAULT,
-                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
+  const auto idmap = Idmap::FromContainers(**target, **overlay, TestConstants::OVERLAY_NAME_DEFAULT,
+                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ true);
   ASSERT_TRUE(idmap);
 
   std::stringstream stream;
@@ -66,7 +64,7 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(
       StringPrintf(ADDRESS "%s  target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING),
       stream.str());
@@ -75,8 +73,6 @@
       stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  fulfilled policies: public\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  enforce overlayable\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  target entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000000  target inline entry count", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  overlay entry count", stream.str());
@@ -104,7 +100,7 @@
 TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
   fclose(stderr);  // silence expected warnings from libandroidfw
 
-  std::string raw(reinterpret_cast<const char*>(idmap_raw_data), kIdmapRawDataLen);
+  std::string raw(reinterpret_cast<const char*>(kIdmapRawData), kIdmapRawDataLen);
   std::istringstream raw_stream(raw);
 
   const auto idmap = Idmap::FromBinaryStream(raw_stream);
@@ -115,7 +111,7 @@
   (*idmap)->accept(&visitor);
 
   ASSERT_CONTAINS_REGEX(ADDRESS "504d4449  magic\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "00000007  version\n", stream.str());
+  ASSERT_CONTAINS_REGEX(ADDRESS "00000008  version\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00001234  target crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00005678  overlay crc\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000011  fulfilled policies: public|signature\n", stream.str());
@@ -126,8 +122,6 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay path: overlayX.apk\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "0000000b  overlay name size\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "........  overlay name: OverlayName\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  target package id\n", stream.str());
-  ASSERT_CONTAINS_REGEX(ADDRESS "      7f  overlay package id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000003  target entry count\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000001  target inline entry count\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000003  overlay entry count\n", stream.str());
@@ -140,7 +134,7 @@
   ASSERT_CONTAINS_REGEX(ADDRESS "7f020000  overlay id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "7f030002  target id\n", stream.str());
   ASSERT_CONTAINS_REGEX(ADDRESS "00000004  string pool size\n", stream.str());
-  ASSERT_CONTAINS_REGEX("000000a8: ........  string pool\n", stream.str());
+  ASSERT_CONTAINS_REGEX("000000a4: ........  string pool\n", stream.str());
 }
 
 }  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 0362529..5a1d808 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#include <android-base/file.h>
+#include <androidfw/ResourceTypes.h>
+#include <gtest/gtest.h>
+
 #include <cstdio>  // fclose
 #include <fstream>
 #include <memory>
@@ -22,14 +26,10 @@
 #include "R.h"
 #include "TestConstants.h"
 #include "TestHelpers.h"
-#include "androidfw/ResourceTypes.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
 #include "idmap2/LogInfo.h"
 #include "idmap2/ResourceMapping.h"
 
 using android::Res_value;
-using android::idmap2::utils::ExtractOverlayManifestInfo;
 
 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
 
@@ -41,32 +41,36 @@
     ASSERT_TRUE(result) << result.GetErrorMessage(); \
   } while (0)
 
-Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_apk_path,
-                                               const std::string& local_overlay_apk_path,
+Result<ResourceMapping> TestGetResourceMapping(const std::string& local_target_path,
+                                               const std::string& local_overlay_path,
                                                const std::string& overlay_name,
                                                const PolicyBitmask& fulfilled_policies,
                                                bool enforce_overlayable) {
-  auto overlay_info =
-      ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path, overlay_name);
+  const std::string target_path = (local_target_path[0] == '/')
+                                      ? local_target_path
+                                      : (GetTestDataPath() + "/" + local_target_path);
+  auto target = TargetResourceContainer::FromPath(target_path);
+  if (!target) {
+    return Error(target.GetError(), R"(Failed to load target "%s")", target_path.c_str());
+  }
+
+  const std::string overlay_path = (local_overlay_path[0] == '/')
+                                       ? local_overlay_path
+                                       : (GetTestDataPath() + "/" + local_overlay_path);
+  auto overlay = OverlayResourceContainer::FromPath(overlay_path);
+  if (!overlay) {
+    return Error(overlay.GetError(), R"(Failed to load overlay "%s")", overlay_path.c_str());
+  }
+
+  auto overlay_info = (*overlay)->FindOverlayInfo(overlay_name);
   if (!overlay_info) {
-    return overlay_info.GetError();
-  }
-
-  const std::string target_apk_path(GetTestDataPath() + local_target_apk_path);
-  std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
-  if (!target_apk) {
-    return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
-  }
-
-  const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path);
-  std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
-  if (!overlay_apk) {
-    return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+    return Error(overlay_info.GetError(), R"(Failed to find overlay name "%s")",
+                 overlay_name.c_str());
   }
 
   LogInfo log_info;
-  return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, *overlay_info,
-                                        fulfilled_policies, enforce_overlayable, log_info);
+  return ResourceMapping::FromContainers(**target, **overlay, *overlay_info, fulfilled_policies,
+                                         enforce_overlayable, log_info);
 }
 
 Result<Unit> MappingExists(const ResourceMapping& mapping, ResourceId target_resource,
@@ -128,7 +132,7 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
@@ -145,7 +149,7 @@
 }
 
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "SwapNames",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "SwapNames",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -161,7 +165,7 @@
 }
 
 TEST(ResourceMappingTests, DoNotRewriteNonOverlayResourceId) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           "DifferentPackages", PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -176,7 +180,7 @@
 }
 
 TEST(ResourceMappingTests, InlineResources) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", "Inline",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk", "Inline",
                                           PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
 
   constexpr size_t overlay_string_pool_size = 10U;
@@ -189,8 +193,32 @@
   ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 73U));
 }
 
+TEST(ResourceMappingTests, FabricatedOverlay) {
+  auto frro = FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "test.target")
+                  .SetOverlayable("TestResources")
+                  .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
+                  .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+                  .Build();
+
+  ASSERT_TRUE(frro);
+  TemporaryFile tf;
+  std::ofstream out(tf.path);
+  ASSERT_TRUE((*frro).ToBinaryStream(out));
+  out.close();
+
+  auto resources = TestGetResourceMapping("target/target.apk", tf.path, "SandTheme",
+                                          PolicyFlags::PUBLIC, /* enforce_overlayable */ false);
+
+  ASSERT_TRUE(resources) << resources.GetErrorMessage();
+  auto& res = *resources;
+  ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+  ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
+  ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+  ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
+}
+
 TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
@@ -209,7 +237,7 @@
 // Resources that are not declared as overlayable and resources that a protected by policies the
 // overlay does not fulfill must not map to overlay resources.
 TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ true);
@@ -229,7 +257,7 @@
 // overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
 // off.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay.apk",
                                           TestConstants::OVERLAY_NAME_ALL_POLICIES,
                                           PolicyFlags::SYSTEM_PARTITION | PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
@@ -264,7 +292,7 @@
 // Overlays that do not target an <overlayable> tag can overlay any resource if overlayable
 // enforcement is disabled.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
-  auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-legacy.apk", "",
+  auto resources = TestGetResourceMapping("target/target.apk", "overlay/overlay-legacy.apk", "",
                                           PolicyFlags::PUBLIC,
                                           /* enforce_overlayable */ false);
 
@@ -284,10 +312,9 @@
 // Overlays that are neither pre-installed nor signed with the same signature as the target cannot
 // overlay packages that have not defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
-  auto resources =
-      TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
-                             "NoTargetName", PolicyFlags::PUBLIC,
-                             /* enforce_overlayable */ true);
+  auto resources = TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
+                                          "NoTargetName", PolicyFlags::PUBLIC,
+                                          /* enforce_overlayable */ true);
 
   ASSERT_TRUE(resources) << resources.GetErrorMessage();
   ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
@@ -297,9 +324,9 @@
 // signed with the same signature as the reference package can overlay packages that have not
 // defined overlayable resources.
 TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
-  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
+  auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) {
     auto resources =
-        TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay.apk",
+        TestGetResourceMapping("target/target-no-overlayable.apk", "overlay/overlay.apk",
                                TestConstants::OVERLAY_NAME_ALL_POLICIES, fulfilled_policies,
                                /* enforce_overlayable */ true);
 
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
index 1f6bf49..6914208 100644
--- a/cmds/idmap2/tests/ResourceUtilsTests.cpp
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -17,10 +17,12 @@
 #include <memory>
 #include <string>
 
+#include "R.h"
 #include "TestHelpers.h"
 #include "androidfw/ApkAssets.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "idmap2/ResourceContainer.h"
 #include "idmap2/ResourceUtils.h"
 #include "idmap2/Result.h"
 
@@ -49,8 +51,8 @@
 };
 
 TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
-  Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000U);
-  ASSERT_TRUE(name);
+  Result<std::string> name = utils::ResToTypeEntryName(GetAssetManager(), R::target::integer::int1);
+  ASSERT_TRUE(name) << name.GetErrorMessage();
   ASSERT_EQ(*name, "integer/int1");
 }
 
@@ -60,25 +62,34 @@
 }
 
 TEST_F(ResourceUtilsTests, InvalidValidOverlayNameInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "InvalidName");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("InvalidName");
   ASSERT_FALSE(info);
 }
 
 TEST_F(ResourceUtilsTests, ValidOverlayNameInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "ValidName");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("ValidName");
   ASSERT_FALSE(info);
 }
 
 TEST_F(ResourceUtilsTests, ValidOverlayNameAndTargetPackageInvalidAttributes) {
-  auto info = utils::ExtractOverlayManifestInfo(GetTestDataPath() + "/overlay/overlay-invalid.apk",
-                                                "ValidNameAndTargetPackage");
+  auto overlay =
+      OverlayResourceContainer::FromPath(GetTestDataPath() + "/overlay/overlay-invalid.apk");
+  ASSERT_TRUE(overlay);
+
+  auto info = (*overlay)->FindOverlayInfo("ValidNameAndTargetPackage");
   ASSERT_TRUE(info);
   ASSERT_EQ("ValidNameAndTargetPackage", info->name);
   ASSERT_EQ("Valid", info->target_package);
-  ASSERT_EQ("", info->target_name); // Attribute resource id could not be found
-  ASSERT_EQ(0, info->resource_mapping); // Attribute resource id could not be found
+  ASSERT_EQ("", info->target_name);      // Attribute resource id could not be found
+  ASSERT_EQ(0, info->resource_mapping);  // Attribute resource id could not be found
 }
 
-}// namespace android::idmap2
+}  // namespace android::idmap2
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 842af3d..6b5f3a8 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -24,13 +24,13 @@
 
 namespace android::idmap2 {
 
-const unsigned char idmap_raw_data[] = {
+const unsigned char kIdmapRawData[] = {
     // IDMAP HEADER
     // 0x0: magic
     0x49, 0x44, 0x4d, 0x50,
 
     // 0x4: version
-    0x07, 0x00, 0x00, 0x00,
+    0x08, 0x00, 0x00, 0x00,
 
     // 0x8: target crc
     0x34, 0x12, 0x00, 0x00,
@@ -70,81 +70,72 @@
     0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
 
     // DATA HEADER
-    // 0x54: target_package_id
-    0x7f,
-
-    // 0x55: overlay_package_id
-    0x7f,
-
-    // 0x56: padding
-    0x00, 0x00,
-
-    // 0x58: target_entry_count
+    // 0x54: target_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x5c: target_inline_entry_count
+    // 0x58: target_inline_entry_count
     0x01, 0x00, 0x00, 0x00,
 
-    // 0x60: overlay_entry_count
+    // 0x5c: overlay_entry_count
     0x03, 0x00, 0x00, 0x00,
 
-    // 0x64: string_pool_offset
+    // 0x60: string_pool_offset
     0x00, 0x00, 0x00, 0x00,
 
     // TARGET ENTRIES
-    // 0x68: target id (0x7f020000)
+    // 0x64: target id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x6c: overlay_id (0x7f020000)
+    // 0x68: overlay_id (0x7f020000)
     0x00, 0x00, 0x02, 0x7f,
 
-    // 0x70: target id (0x7f030000)
+    // 0x6c: target id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x74: overlay_id (0x7f030000)
+    // 0x70: overlay_id (0x7f030000)
     0x00, 0x00, 0x03, 0x7f,
 
-    // 0x78: target id (0x7f030002)
+    // 0x74: target id (0x7f030002)
     0x02, 0x00, 0x03, 0x7f,
 
-    // 0x7c: overlay_id (0x7f030001)
+    // 0x78: overlay_id (0x7f030001)
     0x01, 0x00, 0x03, 0x7f,
 
     // INLINE TARGET ENTRIES
 
-    // 0x80: target_id
+    // 0x7c: target_id
     0x00, 0x00, 0x04, 0x7f,
 
-    // 0x84: Res_value::size (value ignored by idmap)
+    // 0x80: Res_value::size (value ignored by idmap)
     0x08, 0x00,
 
-    // 0x87: Res_value::res0 (value ignored by idmap)
+    // 0x82: Res_value::res0 (value ignored by idmap)
     0x00,
 
-    // 0x88: Res_value::dataType (TYPE_INT_HEX)
+    // 0x83: Res_value::dataType (TYPE_INT_HEX)
     0x11,
 
-    // 0x8c: Res_value::data
+    // 0x84: Res_value::data
     0x78, 0x56, 0x34, 0x12,
 
     // OVERLAY ENTRIES
-    // 0x90: 0x7f020000 -> 0x7f020000
+    // 0x88: 0x7f020000 -> 0x7f020000
     0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
 
-    // 0x98: 0x7f030000 -> 0x7f030000
+    // 0x90: 0x7f030000 -> 0x7f030000
     0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
 
-    // 0xa0: 0x7f030001 -> 0x7f030002
+    // 0x98: 0x7f030001 -> 0x7f030002
     0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f,
 
-    // 0xa4: string pool
+    // 0xa0: string pool
     // string length,
     0x04, 0x00, 0x00, 0x00,
 
-    // 0xa8 string contents "test"
+    // 0xa4 string contents "test"
     0x74, 0x65, 0x73, 0x74};
 
-const unsigned int kIdmapRawDataLen = 0xac;
+const unsigned int kIdmapRawDataLen = 0xa8;
 const unsigned int kIdmapRawDataOffset = 0x54;
 const unsigned int kIdmapRawDataTargetCrc = 0x1234;
 const unsigned int kIdmapRawOverlayCrc = 0x5678;
diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp
index 1a7eaca..eaf10a7 100644
--- a/cmds/idmap2/tests/XmlParserTests.cpp
+++ b/cmds/idmap2/tests/XmlParserTests.cpp
@@ -19,25 +19,25 @@
 #include <string>
 
 #include "TestHelpers.h"
-#include "gmock/gmock.h"
+#include "androidfw/AssetsProvider.h"
 #include "gtest/gtest.h"
 #include "idmap2/XmlParser.h"
-#include "idmap2/ZipFile.h"
 
 namespace android::idmap2 {
 
-Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+Result<XmlParser> CreateTestParser(const std::string& test_file) {
+  auto zip = ZipAssetsProvider::Create(GetTestDataPath() + "/target/target.apk");
   if (zip == nullptr) {
     return Error("Failed to open zip file");
   }
 
-  auto data = zip->Uncompress(test_file);
+  auto data = zip->Open(test_file);
   if (data == nullptr) {
     return Error("Failed to open xml file");
   }
 
-  return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
+  return XmlParser::Create(data->getBuffer(true /* aligned*/), data->getLength(),
+                           /* copy_data */ true);
 }
 
 TEST(XmlParserTests, Create) {
@@ -54,7 +54,7 @@
   auto xml = CreateTestParser("res/xml/test.xml");
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
-  auto root_iter = (*xml)->tree_iterator();
+  auto root_iter = xml->tree_iterator();
   ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
   ASSERT_EQ(root_iter->name(), "a");
 
@@ -85,7 +85,7 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter = (*xml)->tree_iterator();
+  auto root_iter = xml->tree_iterator();
 
   // Start at the <b> tag.
   auto a_iter = root_iter.begin();
@@ -111,8 +111,8 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter_1 = (*xml)->tree_iterator();
-  auto root_iter_2 = (*xml)->tree_iterator();
+  auto root_iter_1 = xml->tree_iterator();
+  auto root_iter_2 = xml->tree_iterator();
   ASSERT_EQ(root_iter_1, root_iter_2);
   ASSERT_EQ(*root_iter_1, *root_iter_2);
 
@@ -146,7 +146,7 @@
   ASSERT_TRUE(xml) << xml.GetErrorMessage();
 
   // Start at the <a> tag.
-  auto root_iter_1 = (*xml)->tree_iterator();
+  auto root_iter_1 = xml->tree_iterator();
 
   // Start at the <b> tag.
   auto a_iter_1 = root_iter_1.begin();
diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp
deleted file mode 100644
index 3fca436..0000000
--- a/cmds/idmap2/tests/ZipFileTests.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#include <cstdio>  // fclose
-#include <string>
-
-#include "TestHelpers.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "idmap2/Result.h"
-#include "idmap2/ZipFile.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace android::idmap2 {
-
-TEST(ZipFileTests, BasicOpen) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  fclose(stderr);  // silence expected warnings from libziparchive
-  auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
-  ASSERT_THAT(fail, IsNull());
-}
-
-TEST(ZipFileTests, Crc) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  Result<uint32_t> crc = zip->Crc("AndroidManifest.xml");
-  ASSERT_TRUE(crc);
-  ASSERT_EQ(*crc, 0x762f3d24);
-
-  Result<uint32_t> crc2 = zip->Crc("does-not-exist");
-  ASSERT_FALSE(crc2);
-}
-
-TEST(ZipFileTests, Uncompress) {
-  auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
-  ASSERT_THAT(zip, NotNull());
-
-  auto data = zip->Uncompress("assets/lorem-ipsum.txt");
-  ASSERT_THAT(data, NotNull());
-  const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
-  ASSERT_THAT(data->size, lorem_ipsum.size());
-  ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
-
-  auto fail = zip->Uncompress("does-not-exist");
-  ASSERT_THAT(fail, IsNull());
-}
-
-}  // namespace android::idmap2
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 227b9e2..115c1f2 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -55,7 +55,11 @@
                 + "       svc usb getGadgetHalVersion\n"
                 + "         Gets current Gadget Hal Version\n"
                 + "         possible values of Hal version are any of 'unknown', 'V1_0', 'V1_1',\n"
-                + "         'V1_2'\n";
+                + "         'V1_2'\n"
+                + "       svc usb getUsbHalVersion\n"
+                + "         Gets current USB Hal Version\n"
+                + "         possible values of Hal version are any of 'unknown', 'V1_0', 'V1_1',\n"
+                + "         'V1_2', 'V1_3'\n";
     }
 
     @Override
@@ -111,6 +115,25 @@
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
                 return;
+            } else if ("getUsbHalVersion".equals(args[1])) {
+                try {
+                    int version = usbMgr.getUsbHalVersion();
+
+                    if (version == 13) {
+                        System.err.println("V1_3");
+                    } else if (version == 12) {
+                        System.err.println("V1_2");
+                    } else if (version == 11) {
+                        System.err.println("V1_1");
+                    } else if (version == 10) {
+                        System.err.println("V1_0");
+                    } else {
+                        System.err.println("unknown");
+                    }
+                } catch (RemoteException e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
+                return;
             }
         }
         System.err.println(longHelp());
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt
index 90a526b..48aa8b2 100644
--- a/config/hiddenapi-unsupported.txt
+++ b/config/hiddenapi-unsupported.txt
@@ -204,7 +204,6 @@
 Landroid/os/IUpdateEngine$Stub;-><init>()V
 Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager;
-Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService;
 Landroid/os/storage/IObbActionListener$Stub;-><init>()V
 Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager;
diff --git a/core/api/current.txt b/core/api/current.txt
index 838dd55..9cbf12f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -435,6 +435,7 @@
     field public static final int clickable = 16842981; // 0x10100e5
     field public static final int clipChildren = 16842986; // 0x10100ea
     field public static final int clipOrientation = 16843274; // 0x101020a
+    field public static final int clipToOutline = 16844328; // 0x1010628
     field public static final int clipToPadding = 16842987; // 0x10100eb
     field public static final int closeIcon = 16843905; // 0x1010481
     field @Deprecated public static final int codes = 16843330; // 0x1010242
@@ -572,6 +573,7 @@
     field public static final int dropDownWidth = 16843362; // 0x1010262
     field public static final int duplicateParentState = 16842985; // 0x10100e9
     field public static final int duration = 16843160; // 0x1010198
+    field public static final int edgeEffectType = 16844329; // 0x1010629
     field public static final int editTextBackground = 16843602; // 0x1010352
     field public static final int editTextColor = 16843601; // 0x1010351
     field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -847,6 +849,7 @@
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
     field public static final int killAfterRestore = 16843420; // 0x101029c
+    field public static final int knownCerts = 16844330; // 0x101062a
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235
@@ -1182,6 +1185,7 @@
     field public static final int right = 16843183; // 0x10101af
     field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
     field public static final int ringtoneType = 16843257; // 0x10101f9
+    field public static final int rippleStyle = 16844337; // 0x1010631
     field public static final int rollbackDataPolicy = 16844311; // 0x1010617
     field public static final int rotation = 16843558; // 0x1010326
     field public static final int rotationAnimation = 16844090; // 0x101053a
@@ -1292,6 +1296,7 @@
     field public static final int spinnerMode = 16843505; // 0x10102f1
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
+    field public static final int splashScreenTheme = 16844336; // 0x1010630
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
     field public static final int splitName = 16844105; // 0x1010549
     field public static final int splitTrack = 16843852; // 0x101044c
@@ -1611,6 +1616,7 @@
     field public static final int windowAllowReturnTransitionOverlap = 16843835; // 0x101043b
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
+    field public static final int windowBackgroundBlurRadius = 16844331; // 0x101062b
     field public static final int windowBackgroundFallback = 16844035; // 0x1010503
     field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
     field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
@@ -1651,6 +1657,10 @@
     field public static final int windowShowAnimation = 16842934; // 0x10100b6
     field public static final int windowShowWallpaper = 16843410; // 0x1010292
     field public static final int windowSoftInputMode = 16843307; // 0x101022b
+    field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d
+    field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e
+    field public static final int windowSplashScreenBackground = 16844332; // 0x101062c
+    field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f
     field public static final int windowSplashscreenContent = 16844132; // 0x1010564
     field @Deprecated public static final int windowSwipeToDismiss = 16843763; // 0x10103f3
     field public static final int windowTitleBackgroundStyle = 16842844; // 0x101005c
@@ -1737,6 +1747,9 @@
     field public static final int dialog_min_width_minor = 17104900; // 0x1050004
     field public static final int notification_large_icon_height = 17104902; // 0x1050006
     field public static final int notification_large_icon_width = 17104901; // 0x1050005
+    field public static final int system_app_widget_background_radius = 17104904; // 0x1050008
+    field public static final int system_app_widget_inner_radius = 17104905; // 0x1050009
+    field public static final int system_app_widget_internal_padding = 17104906; // 0x105000a
     field public static final int thumbnail_height = 17104897; // 0x1050001
     field public static final int thumbnail_width = 17104898; // 0x1050002
   }
@@ -2936,12 +2949,12 @@
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
-    field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
     field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
     field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
     field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
+    field public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
@@ -3861,6 +3874,7 @@
     method @Nullable public android.net.Uri getReferrer();
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
+    method @NonNull public final android.window.SplashScreen getSplashScreen();
     method public int getTaskId();
     method public final CharSequence getTitle();
     method public final int getTitleColor();
@@ -5554,15 +5568,19 @@
     field public static final int DEFAULT_LIGHTS = 4; // 0x4
     field public static final int DEFAULT_SOUND = 1; // 0x1
     field public static final int DEFAULT_VIBRATE = 2; // 0x2
+    field public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
     field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
     field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
     field public static final String EXTRA_BIG_TEXT = "android.bigText";
+    field public static final String EXTRA_CALL_PERSON = "android.callPerson";
     field public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
     field public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
     field public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
     field public static final String EXTRA_COLORIZED = "android.colorized";
     field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+    field public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+    field public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
     field public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final String EXTRA_INFO_TEXT = "android.infoText";
     field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
@@ -5580,10 +5598,10 @@
     field public static final String EXTRA_PROGRESS = "android.progress";
     field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
-    field public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
     field public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
     field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
     field @Deprecated public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+    field public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = "android.showBigPictureWhenCollapsed";
     field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
     field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
     field @Deprecated public static final String EXTRA_SMALL_ICON = "android.icon";
@@ -5594,6 +5612,8 @@
     field public static final String EXTRA_TEXT_LINES = "android.textLines";
     field public static final String EXTRA_TITLE = "android.title";
     field public static final String EXTRA_TITLE_BIG = "android.title.big";
+    field public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+    field public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
     field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
     field public static final int FLAG_BUBBLE = 4096; // 0x1000
     field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
@@ -5842,6 +5862,14 @@
     method @NonNull public android.app.Notification.Builder setWhen(long);
   }
 
+  public static class Notification.CallStyle extends android.app.Notification.Style {
+    method @NonNull public static android.app.Notification.CallStyle forIncomingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+    method @NonNull public static android.app.Notification.CallStyle forOngoingCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent);
+    method @NonNull public static android.app.Notification.CallStyle forScreeningCall(@NonNull android.app.Person, @NonNull android.app.PendingIntent, @NonNull android.app.PendingIntent);
+    method @NonNull public android.app.Notification.CallStyle setVerificationIcon(@Nullable android.graphics.drawable.Icon);
+    method @NonNull public android.app.Notification.CallStyle setVerificationText(@Nullable CharSequence);
+  }
+
   public static final class Notification.CarExtender implements android.app.Notification.Extender {
     ctor public Notification.CarExtender();
     ctor public Notification.CarExtender(android.app.Notification);
@@ -6635,6 +6663,7 @@
     method @NonNull public java.time.LocalTime getCustomNightModeEnd();
     method @NonNull public java.time.LocalTime getCustomNightModeStart();
     method public int getNightMode();
+    method public void setApplicationNightMode(int);
     method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
     method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
     method public void setNightMode(int);
@@ -6879,6 +6908,7 @@
     method public void onLockTaskModeEntering(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull String);
     method public void onLockTaskModeExiting(@NonNull android.content.Context, @NonNull android.content.Intent);
     method public void onNetworkLogsAvailable(@NonNull android.content.Context, @NonNull android.content.Intent, long, @IntRange(from=1) int);
+    method public void onOperationSafetyStateChanged(@NonNull android.content.Context, int, boolean);
     method @Deprecated public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent);
     method public void onPasswordChanged(@NonNull android.content.Context, @NonNull android.content.Intent, @NonNull android.os.UserHandle);
     method @Deprecated public void onPasswordExpiring(@NonNull android.content.Context, @NonNull android.content.Intent);
@@ -6932,6 +6962,7 @@
     method public void addUserRestriction(@NonNull android.content.ComponentName, String);
     method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method public boolean canAdminGrantSensorsPermissions();
+    method public boolean canUsbDataSignalingBeDisabled();
     method public void clearApplicationUserData(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener);
     method public void clearCrossProfileIntentFilters(@NonNull android.content.ComponentName);
     method @Deprecated public void clearDeviceOwnerApp(String);
@@ -7055,9 +7086,11 @@
     method public boolean isProfileOwnerApp(String);
     method public boolean isProvisioningAllowed(@NonNull String);
     method public boolean isResetPasswordTokenActive(android.content.ComponentName);
+    method public boolean isSafeOperation(int);
     method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
     method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
     method public boolean isUniqueDeviceAttestationSupported();
+    method public boolean isUsbDataSignalingEnabled();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
     method public void lockNow();
     method public void lockNow(int);
@@ -7159,6 +7192,7 @@
     method public boolean setTimeZone(@NonNull android.content.ComponentName, String);
     method public void setTrustAgentConfiguration(@NonNull android.content.ComponentName, @NonNull android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(@Nullable android.content.ComponentName, String, boolean);
+    method public void setUsbDataSignalingEnabled(boolean);
     method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap);
     method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
@@ -7227,7 +7261,7 @@
     field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
     field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
-    field public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
+    field @Deprecated public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
     field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
     field public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
     field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
@@ -7283,6 +7317,7 @@
     field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
     field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+    field public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; // 0x1
     field public static final int PASSWORD_COMPLEXITY_HIGH = 327680; // 0x50000
     field public static final int PASSWORD_COMPLEXITY_LOW = 65536; // 0x10000
     field public static final int PASSWORD_COMPLEXITY_MEDIUM = 196608; // 0x30000
@@ -7319,7 +7354,6 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
-    field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
     field public static final int WIPE_EUICC = 4; // 0x4
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7473,7 +7507,7 @@
 
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
     method public int describeContents();
-    method public int getReason();
+    method @NonNull public java.util.List<java.lang.Integer> getReasons();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
   }
@@ -8275,6 +8309,7 @@
   public class AppWidgetHostView extends android.widget.FrameLayout {
     ctor public AppWidgetHostView(android.content.Context);
     ctor public AppWidgetHostView(android.content.Context, int, int);
+    method public void clearCurrentSize();
     method public int getAppWidgetId();
     method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo();
     method public static android.graphics.Rect getDefaultPaddingForWidget(android.content.Context, android.content.ComponentName, android.graphics.Rect);
@@ -8282,11 +8317,13 @@
     method protected android.view.View getErrorView();
     method protected void prepareView(android.view.View);
     method public void setAppWidget(int, android.appwidget.AppWidgetProviderInfo);
+    method public void setCurrentSize(@NonNull android.graphics.PointF);
     method public void setExecutor(java.util.concurrent.Executor);
     method public void setOnLightBackground(boolean);
     method public void updateAppWidget(android.widget.RemoteViews);
     method public void updateAppWidgetOptions(android.os.Bundle);
-    method public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
+    method @Deprecated public void updateAppWidgetSize(android.os.Bundle, int, int, int, int);
+    method public void updateAppWidgetSize(@NonNull android.os.Bundle, @NonNull java.util.List<android.graphics.PointF>);
   }
 
   public class AppWidgetManager {
@@ -8339,6 +8376,7 @@
     field public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
     field public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
     field public static final String OPTION_APPWIDGET_RESTORE_COMPLETED = "appWidgetRestoreCompleted";
+    field public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
   }
 
   public class AppWidgetProvider extends android.content.BroadcastReceiver {
@@ -9800,6 +9838,7 @@
     method public int getMimeTypeCount();
     method public long getTimestamp();
     method public boolean hasMimeType(String);
+    method public boolean isStyledText();
     method public void setExtras(android.os.PersistableBundle);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
@@ -10206,12 +10245,15 @@
     method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
+    method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)") public abstract int checkCallingUriPermission(android.net.Uri, int);
+    method @NonNull public int[] checkCallingUriPermissions(@NonNull java.util.List<android.net.Uri>, int);
     method @CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String, int, int);
     method public abstract int checkSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)") public abstract int checkUriPermission(android.net.Uri, int, int, int);
     method @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)") public abstract int checkUriPermission(@Nullable android.net.Uri, @Nullable String, @Nullable String, int, int, int);
+    method @NonNull public int[] checkUriPermissions(@NonNull java.util.List<android.net.Uri>, int, int, int);
     method @Deprecated public abstract void clearWallpaper() throws java.io.IOException;
     method @NonNull public android.content.Context createAttributionContext(@Nullable String);
     method public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration);
@@ -10424,6 +10466,7 @@
     field public static final String USAGE_STATS_SERVICE = "usagestats";
     field public static final String USB_SERVICE = "usb";
     field public static final String USER_SERVICE = "user";
+    field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
     field public static final String VIBRATOR_SERVICE = "vibrator";
     field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
     field public static final String WALLPAPER_SERVICE = "wallpaper";
@@ -10964,6 +11007,7 @@
     field public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
     field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
     field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
+    field public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
     field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
@@ -12756,6 +12800,7 @@
     method @NonNull public android.content.pm.ShortcutInfo.Builder setPersons(@NonNull android.app.Person[]);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setRank(int);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setShortLabel(@NonNull CharSequence);
+    method @NonNull public android.content.pm.ShortcutInfo.Builder setStartingTheme(int);
   }
 
   public class ShortcutManager {
@@ -16493,9 +16538,13 @@
   public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
     ctor public RippleDrawable(@NonNull android.content.res.ColorStateList, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable);
     method public int getRadius();
+    method public int getRippleStyle();
     method public void setColor(android.content.res.ColorStateList);
     method public void setRadius(int);
+    method public void setRippleStyle(int) throws java.lang.IllegalArgumentException;
     field public static final int RADIUS_AUTO = -1; // 0xffffffff
+    field public static final int STYLE_PATTERNED = 1; // 0x1
+    field public static final int STYLE_SOLID = 0; // 0x0
   }
 
   public class RotateDrawable extends android.graphics.drawable.DrawableWrapper {
@@ -21875,6 +21924,7 @@
     field public static final String KEY_COLOR_RANGE = "color-range";
     field public static final String KEY_COLOR_STANDARD = "color-standard";
     field public static final String KEY_COLOR_TRANSFER = "color-transfer";
+    field public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
     field public static final String KEY_COMPLEXITY = "complexity";
     field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
     field public static final String KEY_DURATION = "durationUs";
@@ -23017,10 +23067,12 @@
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
     method public float getVolume();
+    method public boolean isHapticGeneratorEnabled();
     method public boolean isLooping();
     method public boolean isPlaying();
     method public void play();
     method public void setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+    method public boolean setHapticGeneratorEnabled(boolean);
     method public void setLooping(boolean);
     method @Deprecated public void setStreamType(int);
     method public void setVolume(float);
@@ -30594,6 +30646,7 @@
     field public static String DIRECTORY_NOTIFICATIONS;
     field public static String DIRECTORY_PICTURES;
     field public static String DIRECTORY_PODCASTS;
+    field @NonNull public static String DIRECTORY_RECORDINGS;
     field public static String DIRECTORY_RINGTONES;
     field public static String DIRECTORY_SCREENSHOTS;
     field public static final String MEDIA_BAD_REMOVAL = "bad_removal";
@@ -31608,6 +31661,7 @@
     method @NonNull public int[] areEffectsSupported(@NonNull int...);
     method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+    method public int getId();
     method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
@@ -31622,10 +31676,12 @@
   }
 
   public abstract class VibratorManager {
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
     method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
     method @NonNull public abstract android.os.Vibrator getVibrator(int);
     method @NonNull public abstract int[] getVibratorIds();
-    method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
@@ -38049,6 +38105,10 @@
     method public final void setNotificationsShown(String[]);
     method public final void snoozeNotification(String, long);
     method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel);
+    field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2
+    field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1
+    field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8
+    field public static final int FLAG_FILTER_TYPE_SILENT = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38057,6 +38117,7 @@
     field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
     field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+    field public static final String META_DATA_DEFAULT_FILTER_TYPES = "android.service.notification.default_filter_types";
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
@@ -40338,6 +40399,7 @@
     field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
+    field public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = "store_sim_pin_for_unattended_reboot_bool";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
     field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool";
@@ -42011,8 +42073,9 @@
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
     field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
     field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
-    field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+    field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
   }
 
   public class TelephonyManager {
@@ -42063,7 +42126,7 @@
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState();
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -42922,6 +42985,16 @@
     field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2
   }
 
+  public final class ImsRegistrationAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAttributeFlags();
+    method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
+    method public int getTransportType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
+  }
+
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
   }
@@ -42931,7 +43004,6 @@
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
-    field public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1; // 0x1
     field public static final int REGISTRATION_STATE_NOT_REGISTERED = 0; // 0x0
     field public static final int REGISTRATION_STATE_REGISTERED = 2; // 0x2
     field public static final int REGISTRATION_STATE_REGISTERING = 1; // 0x1
@@ -42940,9 +43012,9 @@
   public static class RegistrationManager.RegistrationCallback {
     ctor public RegistrationManager.RegistrationCallback();
     method @Deprecated public void onRegistered(int);
-    method public void onRegistered(int, int);
+    method public void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method @Deprecated public void onRegistering(int);
-    method public void onRegistering(int, int);
+    method public void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo);
   }
@@ -49321,6 +49393,7 @@
     method public void setAllowEnterTransitionOverlap(boolean);
     method public void setAllowReturnTransitionOverlap(boolean);
     method public void setAttributes(android.view.WindowManager.LayoutParams);
+    method public void setBackgroundBlurRadius(int);
     method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setBackgroundDrawableResource(@DrawableRes int);
     method public void setCallback(android.view.Window.Callback);
@@ -50687,6 +50760,7 @@
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
     field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT";
+    field public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
     field public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE";
   }
 
@@ -53554,20 +53628,27 @@
 
   public class EdgeEffect {
     ctor public EdgeEffect(android.content.Context);
+    ctor public EdgeEffect(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     method public boolean draw(android.graphics.Canvas);
     method public void finish();
     method @Nullable public android.graphics.BlendMode getBlendMode();
     method @ColorInt public int getColor();
+    method public float getDistance();
     method public int getMaxHeight();
+    method public int getType();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
     method public void onPull(float, float);
+    method public float onPullDistance(float, float);
     method public void onRelease();
     method public void setBlendMode(@Nullable android.graphics.BlendMode);
     method public void setColor(@ColorInt int);
     method public void setSize(int, int);
+    method public void setType(int);
     field public static final android.graphics.BlendMode DEFAULT_BLEND_MODE;
+    field public static final int TYPE_GLOW = 0; // 0x0
+    field public static final int TYPE_STRETCH = 1; // 0x1
   }
 
   public class EditText extends android.widget.TextView {
@@ -54572,6 +54653,7 @@
   public class RemoteViews implements android.view.LayoutInflater.Filter android.os.Parcelable {
     ctor public RemoteViews(String, int);
     ctor public RemoteViews(android.widget.RemoteViews, android.widget.RemoteViews);
+    ctor public RemoteViews(@NonNull java.util.Map<android.graphics.PointF,android.widget.RemoteViews>);
     ctor public RemoteViews(android.widget.RemoteViews);
     ctor public RemoteViews(android.os.Parcel);
     method public void addView(@IdRes int, android.widget.RemoteViews);
@@ -54634,6 +54716,8 @@
     method public void setTextViewText(@IdRes int, CharSequence);
     method public void setTextViewTextSize(@IdRes int, int, float);
     method public void setUri(@IdRes int, String, android.net.Uri);
+    method public void setViewOutlinePreferredRadius(@IdRes int, float, int);
+    method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
     method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
     method public void setViewVisibility(@IdRes int, int);
     method public void showNext(@IdRes int);
@@ -54658,6 +54742,12 @@
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface RemoteViews.RemoteView {
   }
 
+  public static final class RemoteViews.RemoteViewOutlineProvider extends android.view.ViewOutlineProvider {
+    ctor public RemoteViews.RemoteViewOutlineProvider(float);
+    method public void getOutline(@NonNull android.view.View, @NonNull android.graphics.Outline);
+    method public float getRadius();
+  }
+
   public abstract class RemoteViewsService extends android.app.Service {
     ctor public RemoteViewsService();
     method public android.os.IBinder onBind(android.content.Intent);
@@ -55761,6 +55851,25 @@
 
 }
 
+package android.window {
+
+  public interface SplashScreen {
+    method public void setOnExitAnimationListener(@Nullable android.window.SplashScreen.OnExitAnimationListener);
+  }
+
+  public static interface SplashScreen.OnExitAnimationListener {
+    method public void onSplashScreenExit(@NonNull android.window.SplashScreenView);
+  }
+
+  public final class SplashScreenView extends android.widget.FrameLayout {
+    method public long getIconAnimationDurationMillis();
+    method public long getIconAnimationStartMillis();
+    method @Nullable public android.view.View getIconView();
+    method public void remove();
+  }
+
+}
+
 package javax.microedition.khronos.egl {
 
   public interface EGL {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index bf70803..b57fdf1 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -73,6 +73,7 @@
   public class UsbManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getGadgetHalVersion();
     method public int getUsbBandwidth();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getUsbHalVersion();
     field public static final int GADGET_HAL_NOT_SUPPORTED = -1; // 0xffffffff
     field public static final int GADGET_HAL_V1_0 = 10; // 0xa
     field public static final int GADGET_HAL_V1_1 = 11; // 0xb
@@ -85,6 +86,11 @@
     field public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480; // 0x1e0
     field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2
     field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff
+    field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff
+    field public static final int USB_HAL_V1_0 = 10; // 0xa
+    field public static final int USB_HAL_V1_1 = 11; // 0xb
+    field public static final int USB_HAL_V1_2 = 12; // 0xc
+    field public static final int USB_HAL_V1_3 = 13; // 0xd
   }
 
 }
@@ -162,6 +168,7 @@
   }
 
   public class ConnectivityManager {
+    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, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
   }
@@ -222,6 +229,14 @@
     field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
   }
 
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor public VpnTransportInfo(int);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+    field public final int type;
+  }
+
 }
 
 package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5bb3e05..536d3b6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -27,6 +27,7 @@
     field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
     field public static final String BACKUP = "android.permission.BACKUP";
+    field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
     field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
     field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
     field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
@@ -82,6 +83,7 @@
     field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION";
     field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
     field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
+    field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
     field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
     field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
     field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
@@ -179,6 +181,7 @@
     field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
     field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
     field public static final String PERFORM_CDMA_PROVISIONING = "android.permission.PERFORM_CDMA_PROVISIONING";
+    field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
@@ -244,6 +247,7 @@
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+    field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
     field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
     field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
@@ -338,9 +342,9 @@
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
+    field public static final int config_systemShell = 17039402; // 0x104002a
   }
 
   public static final class R.style {
@@ -678,6 +682,7 @@
   }
 
   public static class Notification.Action implements android.os.Parcelable {
+    field public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; // 0xc
     field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
   }
 
@@ -870,7 +875,6 @@
   }
 
   public class DevicePolicyManager {
-    method public boolean canAdminGrantSensorsPermissionsForUser(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -905,8 +909,8 @@
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
-    field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
-    field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
+    field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
+    field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
     field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
     field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
     field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
@@ -1850,6 +1854,7 @@
   }
 
   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 @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
@@ -2523,7 +2528,8 @@
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
     field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
     field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
-    field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+    field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+    field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version";
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
@@ -2632,6 +2638,7 @@
     field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
     field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
+    field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
     field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -2811,16 +2818,48 @@
 
 package android.graphics.fonts {
 
+  public final class FontFamilyUpdateRequest {
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.FontFamily> getFontFamilies();
+    method @NonNull public java.util.List<android.graphics.fonts.FontFileUpdateRequest> getFontFileUpdateRequests();
+  }
+
+  public static final class FontFamilyUpdateRequest.Builder {
+    ctor public FontFamilyUpdateRequest.Builder();
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.FontFamily);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFileUpdateRequest(@NonNull android.graphics.fonts.FontFileUpdateRequest);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest build();
+  }
+
+  public static final class FontFamilyUpdateRequest.Font {
+    ctor public FontFamilyUpdateRequest.Font(@NonNull String, @NonNull android.graphics.fonts.FontStyle, @NonNull java.util.List<android.graphics.fonts.FontVariationAxis>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontVariationAxis> getAxes();
+    method @NonNull public String getPostScriptName();
+    method @NonNull public android.graphics.fonts.FontStyle getStyle();
+  }
+
+  public static final class FontFamilyUpdateRequest.FontFamily {
+    ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts();
+    method @NonNull public String getName();
+  }
+
+  public final class FontFileUpdateRequest {
+    ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]);
+    method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    method @NonNull public byte[] getSignature();
+  }
+
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -3503,6 +3542,7 @@
     field public static final int RESULT_FAILED_BAD_PARAMS = 2; // 0x2
     field public static final int RESULT_FAILED_BUSY = 4; // 0x4
     field public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; // 0x8
+    field public static final int RESULT_FAILED_PERMISSION_DENIED = 9; // 0x9
     field public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; // 0x7
     field public static final int RESULT_FAILED_TIMEOUT = 6; // 0x6
     field public static final int RESULT_FAILED_UNINITIALIZED = 3; // 0x3
@@ -7044,11 +7084,15 @@
     method public long getExpiryTimeMillis();
     method public long getRefreshTimeMillis();
     method @Nullable public android.net.Uri getUserPortalUrl();
+    method public int getUserPortalUrlSource();
     method @Nullable public String getVenueFriendlyName();
     method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public int getVenueInfoUrlSource();
     method public boolean isCaptive();
     method public boolean isSessionExtendable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
     field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
   }
 
@@ -7062,8 +7106,10 @@
     method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
     method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
     method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
     method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
     method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
   }
 
   public class ConnectivityManager {
@@ -7077,6 +7123,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -7098,6 +7145,10 @@
     field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
   }
 
+  public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
+    method public void onComplete();
+  }
+
   @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
     ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
     method @Deprecated public void onTetheringFailed();
@@ -7182,6 +7233,7 @@
     method public void close();
     method @NonNull public String getInterfaceName();
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
   }
 
   public static class IpSecTransform.Builder {
@@ -7325,6 +7377,7 @@
     method @NonNull public int[] getAdministratorUids();
     method @Nullable public String getSsid();
     method @NonNull public int[] getTransportTypes();
+    method public boolean isPrivateDnsBroken();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
     field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
     field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
@@ -7339,6 +7392,7 @@
     method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
     method @NonNull public android.net.NetworkCapabilities build();
+    method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
     method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
@@ -7454,6 +7508,26 @@
     ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
   }
 
+  public final class OemNetworkPreferences implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR;
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4
+    field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0
+  }
+
+  public static final class OemNetworkPreferences.Builder {
+    ctor public OemNetworkPreferences.Builder();
+    ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences);
+    method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int);
+    method @NonNull public android.net.OemNetworkPreferences build();
+    method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String);
+  }
+
   public abstract class QosCallback {
     ctor public QosCallback();
     method public void onError(@NonNull android.net.QosCallbackException);
@@ -8492,7 +8566,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
-    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
     method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
@@ -8562,6 +8636,7 @@
   public class SystemConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String);
   }
 
   public class SystemProperties {
@@ -9512,10 +9587,12 @@
   }
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
+    method @Nullable public int[] getAttestationIds();
     method public int getNamespace();
   }
 
   public static final class KeyGenParameterSpec.Builder {
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int);
     method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
   }
@@ -10348,6 +10425,7 @@
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
     method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException;
+    method public long onGetAnrDelayMillis(@NonNull String, int);
     method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException;
     method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException;
     field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
@@ -11039,6 +11117,8 @@
   }
 
   public static final class CarrierConfigManager.Wifi {
+    field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool";
+    field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool";
     field public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT = "wifi.hotspot_maximum_client_count";
     field public static final String KEY_PREFIX = "wifi.";
     field public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED = "wifi.suggestion_ssid_list_with_mac_randomization_disabled";
@@ -11869,6 +11949,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+    method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -11939,6 +12020,7 @@
     field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
     field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
+    field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
     field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
@@ -11993,6 +12075,9 @@
     field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2
     field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3
     field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1
+    field public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2; // 0x2
+    field public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; // 0x1
+    field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
     field public static final int RADIO_POWER_OFF = 0; // 0x0
     field public static final int RADIO_POWER_ON = 1; // 0x1
     field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -12974,6 +13059,16 @@
     field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
   }
 
+  public final class ImsRegistrationAttributes implements android.os.Parcelable {
+    method public int getRegistrationTechnology();
+  }
+
+  public static final class ImsRegistrationAttributes.Builder {
+    ctor public ImsRegistrationAttributes.Builder(int);
+    method @NonNull public android.telephony.ims.ImsRegistrationAttributes build();
+    method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFeatureTags(@NonNull java.util.Set<java.lang.String>);
+  }
+
   public class ImsService extends android.app.Service {
     ctor public ImsService();
     method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -13757,7 +13852,9 @@
     ctor public ImsRegistrationImplBase();
     method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
     method public final void onRegistered(int);
+    method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onRegistering(int);
+    method public final void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes);
     method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]);
     method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo);
     method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e39b2b8..2a9b318 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -14,6 +14,7 @@
     field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
     field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
+    field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES";
     field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
     field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
@@ -53,9 +54,8 @@
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
-    field public static final int config_systemAutomotiveProjection = 17039402; // 0x104002a
+    field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
     field public static final int config_systemGallery = 17039399; // 0x1040027
-    field public static final int config_systemVideoCall = 17039401; // 0x1040029
   }
 
 }
@@ -391,11 +391,11 @@
     method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
+    method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
     method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
-    method @NonNull public static String unsafeOperationReasonToString(int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
     field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
@@ -425,6 +425,7 @@
     field public static final int OPERATION_REMOVE_KEY_PAIR = 28; // 0x1c
     field public static final int OPERATION_REMOVE_USER = 6; // 0x6
     field public static final int OPERATION_REQUEST_BUGREPORT = 29; // 0x1d
+    field public static final int OPERATION_SAFETY_REASON_NONE = -1; // 0xffffffff
     field public static final int OPERATION_SET_ALWAYS_ON_VPN_PACKAGE = 30; // 0x1e
     field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
     field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
@@ -460,7 +461,6 @@
     field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
     field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
     field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
-    field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
   }
 
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
@@ -472,6 +472,7 @@
     method @NonNull public String getOwnerName();
     method @Nullable public String getTimeZone();
     method public boolean isLeaveAllSystemAppsEnabled();
+    method public void logParams(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
   }
@@ -495,6 +496,7 @@
     method public boolean isKeepAccountMigrated();
     method public boolean isLeaveAllSystemAppsEnabled();
     method public boolean isOrganizationOwnedProvisioning();
+    method public void logParams(@NonNull String);
     method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
   }
@@ -691,6 +693,7 @@
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
     method public void holdLock(android.os.IBinder, int);
+    method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
@@ -834,14 +837,15 @@
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
@@ -911,6 +915,8 @@
     method @NonNull public int[] getSupportedStates();
     method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
     method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+    field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
+    field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
   }
 
   public static interface DeviceStateManager.DeviceStateListener {
@@ -1343,6 +1349,12 @@
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
+  public class NetworkPolicyManager {
+    method public boolean getRestrictBackground();
+    method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration);
+    method public void setRestrictBackground(boolean);
+  }
+
   public class NetworkStack {
     method public static void setServiceForTest(@Nullable android.os.IBinder);
   }
@@ -1369,6 +1381,32 @@
     field public static final int RESOURCES_SDK_INT;
   }
 
+  public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+    method public abstract long getDuration();
+  }
+
+  public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.os.VibrationEffect getEffect();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
+    method @NonNull public java.util.List<java.lang.Integer> getDelays();
+    method public long getDuration();
+    method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
+  }
+
+  public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
+    method public long getDuration();
+    method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
+  }
+
   public class DeviceIdleManager {
     method @NonNull public String[] getSystemPowerWhitelist();
     method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
@@ -2692,6 +2730,10 @@
     field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
   }
 
+  public final class SplashScreenView extends android.widget.FrameLayout {
+    method @Nullable public android.view.View getBrandingView();
+  }
+
   public final class StartingWindowInfo implements android.os.Parcelable {
     ctor public StartingWindowInfo();
     method public int describeContents();
@@ -2711,6 +2753,7 @@
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
     method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
+    method @BinderThread public void copySplashScreenView(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 3c67e44..87fb5b1 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -487,6 +487,8 @@
     
 GetterSetterNames: android.location.LocationRequest#isLowPowerMode():
     
+GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground():
+    Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground`
 GetterSetterNames: android.os.IncidentReportArgs#isAll():
     
 GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied():
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 4b2d741..768ec38 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -20,12 +20,12 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -97,10 +97,10 @@
           GESTURE_UNKNOWN,
           GESTURE_TOUCH_EXPLORATION,
             GESTURE_2_FINGER_SINGLE_TAP,
-            GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_DOUBLE_TAP,
             GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_TRIPLE_TAP,
+            GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_SINGLE_TAP,
             GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_DOUBLE_TAP,
@@ -232,8 +232,8 @@
             case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
             case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
-            case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
-                return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
+            case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
             case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
                 return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1fa7fa2..dab4a5d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -445,8 +445,8 @@
     /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
     public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
 
-    /** The user has performed a two-finger  single-tap and hold gesture on the touch screen. */
-    public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43;
+    /** The user has performed a two-finger  triple-tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43;
 
     /** The user has performed a three-finger  single-tap and hold gesture on the touch screen. */
     public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/core/java/android/accounts/OWNERS
+++ b/core/java/android/accounts/OWNERS
@@ -3,7 +3,6 @@
 sandrakwan@google.com
 hackbod@google.com
 svetoslavganov@google.com
-moltmann@google.com
 fkupolov@google.com
 yamasani@google.com
 omakoto@google.com
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4728f11..992d054 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -139,6 +139,8 @@
 import android.widget.AdapterView;
 import android.widget.Toast;
 import android.widget.Toolbar;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -961,6 +963,10 @@
 
     private UiTranslationController mUiTranslationController;
 
+    private SplashScreen mSplashScreen;
+    /** @hide */
+    SplashScreenView mSplashScreenView;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -1603,6 +1609,23 @@
     }
 
     /**
+     * Get the interface that activity use to talk to the splash screen.
+     * @see SplashScreen
+     */
+    public final @NonNull SplashScreen getSplashScreen() {
+        return getOrCreateSplashScreen();
+    }
+
+    private SplashScreen getOrCreateSplashScreen() {
+        synchronized (this) {
+            if (mSplashScreen == null) {
+                mSplashScreen = new SplashScreen.SplashScreenImpl(this);
+            }
+            return mSplashScreen;
+        }
+    }
+
+    /**
      * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
      * the attribute {@link android.R.attr#persistableMode} set to
      * <code>persistAcrossReboots</code>.
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 401f8cc..e3b5e9a 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -46,9 +46,9 @@
     }
 
     /** Reports {@link Activity#onResume()} is done. */
-    public void activityResumed(IBinder token) {
+    public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
         try {
-            getActivityClientController().activityResumed(token);
+            getActivityClientController().activityResumed(token, handleSplashScreenExit);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -488,6 +488,17 @@
         }
     }
 
+    /**
+     * Reports the splash screen view has attached to client.
+     */
+    void reportSplashScreenAttached(IBinder token) {
+        try {
+            getActivityClientController().splashScreenAttached(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     public static ActivityClient getInstance() {
         return sInstance.get();
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 43d0269..e2426d1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -50,7 +50,9 @@
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Icon;
+import android.hardware.HardwareBuffer;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
@@ -76,6 +78,7 @@
 import android.util.Size;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
+import android.window.TaskSnapshot;
 
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.procstats.ProcessStats;
@@ -1740,6 +1743,62 @@
      */
     public static class RecentTaskInfo extends TaskInfo implements Parcelable {
         /**
+         * @hide
+         */
+        public static class PersistedTaskSnapshotData {
+            /**
+             * The bounds of the task when the last snapshot was taken, may be null if the task is
+             * not yet attached to the hierarchy.
+             * @see {@link android.window.TaskSnapshot#mTaskSize}.
+             * @hide
+             */
+            public @Nullable Point taskSize;
+
+            /**
+             * The content insets of the task when the task snapshot was taken.
+             * @see {@link android.window.TaskSnapshot#mContentInsets}.
+             * @hide
+             */
+            public @Nullable Rect contentInsets;
+
+            /**
+             * The size of the last snapshot taken, may be null if there is no associated snapshot.
+             * @see {@link android.window.TaskSnapshot#mSnapshot}.
+             * @hide
+             */
+            public @Nullable Point bufferSize;
+
+            /**
+             * Sets the data from the other data.
+             * @hide
+             */
+            public void set(PersistedTaskSnapshotData other) {
+                taskSize = other.taskSize;
+                contentInsets = other.contentInsets;
+                bufferSize = other.bufferSize;
+            }
+
+            /**
+             * Sets the data from the provided {@param snapshot}.
+             * @hide
+             */
+            public void set(TaskSnapshot snapshot) {
+                if (snapshot == null) {
+                    taskSize = null;
+                    contentInsets = null;
+                    bufferSize = null;
+                    return;
+                }
+                final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+                taskSize = new Point(snapshot.getTaskSize());
+                contentInsets = new Rect(snapshot.getContentInsets());
+                bufferSize = buffer != null
+                        ? new Point(buffer.getWidth(), buffer.getHeight())
+                        : null;
+            }
+        }
+
+        /**
          * If this task is currently running, this is the identifier for it.
          * If it is not running, this will be -1.
          *
@@ -1782,6 +1841,12 @@
          */
         public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
 
+        /**
+         * Information about the last snapshot taken for this task.
+         * @hide
+         */
+        public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
+
         public RecentTaskInfo() {
         }
 
@@ -1798,6 +1863,9 @@
             id = source.readInt();
             persistentId = source.readInt();
             childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
+            lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
+            lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
+            lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
             super.readFromParcel(source);
         }
 
@@ -1806,6 +1874,9 @@
             dest.writeInt(id);
             dest.writeInt(persistentId);
             dest.writeList(childrenTaskInfos);
+            dest.writeTypedObject(lastSnapshotData.taskSize, flags);
+            dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
+            dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
             super.writeToParcel(dest, flags);
         }
 
@@ -1825,7 +1896,6 @@
         public void dump(PrintWriter pw, String indent) {
             pw.println(); pw.print("   ");
             pw.print(" id="); pw.print(persistentId);
-            pw.print(" stackId="); pw.print(stackId);
             pw.print(" userId="); pw.print(userId);
             pw.print(" hasTask="); pw.print((id != -1));
             pw.print(" lastActiveTime="); pw.println(lastActiveTime);
@@ -1872,6 +1942,12 @@
                 pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
                 pw.println(" }");
             }
+            pw.print("   ");
+            pw.print(" lastSnapshotData {");
+            pw.print(" taskSize=" + lastSnapshotData.taskSize);
+            pw.print(" contentInsets=" + lastSnapshotData.contentInsets);
+            pw.print(" bufferSize=" + lastSnapshotData.bufferSize);
+            pw.println(" }");
         }
     }
 
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 28da1c3..73cc13c8 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -160,6 +160,12 @@
     public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
 
     /**
+     * Specific a theme for a splash screen window.
+     * @hide
+     */
+    public static final String KEY_SPLASH_SCREEN_THEME = "android.activity.splashScreenTheme";
+
+    /**
      * Callback for when the last frame of the animation is played.
      * @hide
      */
@@ -398,6 +404,7 @@
     private IBinder mLaunchCookie;
     private IRemoteTransition mRemoteTransition;
     private boolean mOverrideTaskTransition;
+    private int mSplashScreenThemeResId;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -1147,6 +1154,7 @@
         mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
                 KEY_REMOTE_TRANSITION));
         mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
+        mSplashScreenThemeResId = opts.getInt(KEY_SPLASH_SCREEN_THEME);
     }
 
     /**
@@ -1333,6 +1341,14 @@
     }
 
     /**
+     * Gets whether the activity want to be launched as other theme for the splash screen.
+     * @hide
+     */
+    public int getSplashScreenThemeResId() {
+        return mSplashScreenThemeResId;
+    }
+
+    /**
      * Sets whether the activity is to be launched into LockTask mode.
      *
      * Use this option to start an activity in LockTask mode. Note that only apps permitted by
@@ -1838,6 +1854,9 @@
         if (mOverrideTaskTransition) {
             b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
         }
+        if (mSplashScreenThemeResId != 0) {
+            b.putInt(KEY_SPLASH_SCREEN_THEME, mSplashScreenThemeResId);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 70fa444..233f737 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -37,6 +38,7 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.view.RemoteAnimationDefinition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 
 import java.util.List;
 
@@ -243,6 +245,22 @@
     }
 
     /**
+     * Notify the server that splash screen of the given task has been copied"
+     *
+     * @param taskId Id of task to handle the material to reconstruct the splash screen view.
+     * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+     * @hide
+     */
+    public void onSplashScreenViewCopyFinished(int taskId,
+            @Nullable SplashScreenViewParcelable parcelable) {
+        try {
+            getService().onSplashScreenViewCopyFinished(taskId, parcelable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the default limit on the number of recents that an app can make.
      * @hide
      */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bb6a774..3d9f612 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -171,12 +171,15 @@
 import android.view.ViewDebug;
 import android.view.ViewManager;
 import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.autofill.AutofillId;
 import android.view.translation.TranslationSpec;
 import android.webkit.WebView;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -185,6 +188,7 @@
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.DecorView;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -227,6 +231,7 @@
 import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
@@ -285,6 +290,7 @@
     /** Use background GC policy and default JIT threshold. */
     private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
 
+    private static final int REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT = 5000;
     /**
      * Denotes an invalid sequence number corresponding to a process state change.
      */
@@ -486,6 +492,8 @@
     final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
 
+    private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;
+
     final GcIdler mGcIdler = new GcIdler();
     final PurgeIdler mPurgeIdler = new PurgeIdler();
 
@@ -1930,6 +1938,8 @@
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
 
+        public static final int REMOVE_SPLASH_SCREEN_VIEW = 172;
+
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -1976,6 +1986,8 @@
                     case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
                     case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                         return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
+                    case REMOVE_SPLASH_SCREEN_VIEW:
+                        return "REMOVE_SPLASH_SCREEN_VIEW";
                 }
             }
             return Integer.toString(code);
@@ -2169,6 +2181,9 @@
                 case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                     handleFinishInstrumentationWithoutRestart();
                     break;
+                case REMOVE_SPLASH_SCREEN_VIEW:
+                    handleRemoveSplashScreenView((ActivityClientRecord) msg.obj);
+                    break;
             }
             Object obj = msg.obj;
             if (obj instanceof SomeArgs) {
@@ -3955,6 +3970,106 @@
     }
 
     /**
+     * Register a splash screen manager to this process.
+     */
+    public void registerSplashScreenManager(
+            @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
+        synchronized (this) {
+            mSplashScreenGlobal = manager;
+        }
+    }
+
+    @Override
+    public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
+        synchronized (this) {
+            return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
+        }
+    }
+
+    @Override
+    public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+            @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+        final DecorView decorView = (DecorView) r.window.peekDecorView();
+        if (parcelable != null && decorView != null) {
+            createSplashScreen(r, decorView, parcelable);
+        } else {
+            // shouldn't happen!
+            Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
+        }
+    }
+
+    private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
+            SplashScreenView.SplashScreenViewParcelable parcelable) {
+        final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
+        final SplashScreenView view = builder.createFromParcel(parcelable).build();
+        decorView.addView(view);
+        view.cacheRootWindow(r.window);
+        view.makeSystemUIColorsTransparent();
+        r.activity.mSplashScreenView = view;
+        view.requestLayout();
+        // Ensure splash screen view is shown before remove the splash screen window.
+        final ViewRootImpl impl = decorView.getViewRootImpl();
+        final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
+        final AtomicBoolean notified = new AtomicBoolean();
+        if (hardwareEnabled) {
+            final Runnable frameCommit = new Runnable() {
+                        @Override
+                        public void run() {
+                            view.post(() -> {
+                                if (!notified.get()) {
+                                    view.getViewTreeObserver().unregisterFrameCommitCallback(this);
+                                    ActivityClient.getInstance().reportSplashScreenAttached(
+                                            r.token);
+                                    notified.set(true);
+                                }
+                            });
+                        }
+                    };
+            view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
+        } else {
+            final ViewTreeObserver.OnDrawListener onDrawListener =
+                    new ViewTreeObserver.OnDrawListener() {
+                        @Override
+                        public void onDraw() {
+                            view.post(() -> {
+                                if (!notified.get()) {
+                                    view.getViewTreeObserver().removeOnDrawListener(this);
+                                    ActivityClient.getInstance().reportSplashScreenAttached(
+                                            r.token);
+                                    notified.set(true);
+                                }
+                            });
+                        }
+                    };
+            view.getViewTreeObserver().addOnDrawListener(onDrawListener);
+        }
+    }
+
+    @Override
+    public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
+        if (r.activity.mSplashScreenView != null) {
+            Message msg = mH.obtainMessage(H.REMOVE_SPLASH_SCREEN_VIEW, r);
+            mH.sendMessageDelayed(msg, REMOVE_SPLASH_SCREEN_VIEW_TIMEOUT);
+            synchronized (this) {
+                if (mSplashScreenGlobal != null) {
+                    mSplashScreenGlobal.dispatchOnExitAnimation(r.token,
+                            r.activity.mSplashScreenView);
+                }
+            }
+        }
+    }
+
+    /**
+     * Force remove splash screen view.
+     */
+    private void handleRemoveSplashScreenView(@NonNull ActivityClientRecord r) {
+        if (r.activity.mSplashScreenView != null) {
+            r.activity.mSplashScreenView.remove();
+            r.activity.mSplashScreenView = null;
+        }
+    }
+
+    /**
      * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
      * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
      * onPictureInPictureRequested to enter picture-in-picture.
@@ -5174,6 +5289,11 @@
         r.setState(ON_DESTROY);
         mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         schedulePurgeIdler();
+        synchronized (this) {
+            if (mSplashScreenGlobal != null) {
+                mSplashScreenGlobal.tokenDestroyed(r.token);
+            }
+        }
         // updatePendingActivityConfiguration() reads from mActivities to update
         // ActivityClientRecord which runs in a different thread. Protect modifications to
         // mActivities to avoid race.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 0e1c827..cf5fd14 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
 import android.os.IBinder;
 import android.util.MergedConfiguration;
 import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
@@ -158,6 +159,16 @@
     /** Request that an activity enter picture-in-picture. */
     public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
 
+    /** Whether the activity want to handle splash screen exit animation */
+    public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token);
+
+    /** Attach a splash screen window view to the top of the activity */
+    public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
+            @NonNull SplashScreenViewParcelable parcelable);
+
+    /** Hand over the splash screen window view to the activity */
+    public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+
     /** Perform activity launch. */
     public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9a20e0f..85fb543 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.StrictMode.vmIncorrectContextUseEnabled;
 
@@ -105,6 +106,7 @@
 import java.nio.ByteOrder;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -2181,6 +2183,18 @@
         }
     }
 
+    @NonNull
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            int modeFlags) {
+        try {
+            return ActivityManager.getService().checkUriPermissions(uris, pid, uid, modeFlags,
+                    null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** @hide */
     @Override
     public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -2207,12 +2221,30 @@
         return PackageManager.PERMISSION_DENIED;
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        int pid = Binder.getCallingPid();
+        if (pid != Process.myPid()) {
+            return checkUriPermissions(uris, pid, Binder.getCallingUid(), modeFlags);
+        }
+        int[] res = new int[uris.size()];
+        Arrays.fill(res, PERMISSION_DENIED);
+        return res;
+    }
+
     @Override
     public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
         return checkUriPermission(uri, Binder.getCallingPid(),
                 Binder.getCallingUid(), modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return checkUriPermissions(uris, Binder.getCallingPid(), Binder.getCallingUid(), modeFlags);
+    }
+
     @Override
     public int checkUriPermission(Uri uri, String readPermission,
             String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9d3286f..bb743b8 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -34,7 +34,7 @@
  */
 interface IActivityClientController {
     oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
-    oneway void activityResumed(in IBinder token);
+    oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
     oneway void activityTopResumedStateLost();
     /**
      * Notifies that the activity has completed paused. This call is not one-way because it can make
@@ -142,4 +142,7 @@
      * on the back stack.
      */
     oneway void onBackPressedOnTaskRoot(in IBinder token);
+
+    /** Reports that the splash screen view has attached to activity.  */
+    oneway void splashScreenAttached(in IBinder token);
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e73636f..4ad13e1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -220,6 +220,8 @@
     int getProcessLimit();
     int checkUriPermission(in Uri uri, int pid, int uid, int mode, int userId,
             in IBinder callerToken);
+    int[] checkUriPermissions(in List<Uri> uris, int pid, int uid, int mode,
+                in IBinder callerToken);
     void grantUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
             int mode, int userId);
     void revokeUriPermission(in IApplicationThread caller, in String targetPkg, in Uri uri,
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 38a3e70..542f754 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -70,6 +70,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
 
@@ -348,4 +349,10 @@
      * Clears launch params for given packages.
      */
     void clearLaunchParamsForPackages(in List<String> packageNames);
+
+    /**
+     * A splash screen view has copied.
+     */
+    void onSplashScreenViewCopyFinished(int taskId,
+            in SplashScreenView.SplashScreenViewParcelable material);
 }
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
similarity index 73%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/app/ILocalWallpaperColorConsumer.aidl
index 14d57bf..28b11ec 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/ILocalWallpaperColorConsumer.aidl
@@ -14,6 +14,14 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app;
 
-parcelable ExternalTimeSuggestion;
+import android.app.WallpaperColors;
+import android.graphics.RectF;
+
+/**
+ * @hide
+ */
+oneway interface ILocalWallpaperColorConsumer {
+    void onColorsChanged(in RectF area, in WallpaperColors colors);
+}
\ No newline at end of file
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 0ba5bec..f71eebdc 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -62,6 +62,16 @@
     int getNightMode();
 
     /**
+     * Sets the dark mode for the given application. This setting is persisted and will override the
+     * system configuration for this application.
+     *   1 - notnight mode
+     *   2 - night mode
+     *   3 - automatic mode switching
+     * @throws RemoteException
+     */
+    void setApplicationNightMode(in int mode);
+
+    /**
      * Tells if UI mode is locked or not.
      */
     boolean isUiModeLocked();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 101917b..5402381 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -17,9 +17,11 @@
 package android.app;
 
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.app.IWallpaperManagerCallback;
+import android.app.ILocalWallpaperColorConsumer;
 import android.app.WallpaperInfo;
 import android.content.ComponentName;
 import android.app.WallpaperColors;
@@ -162,6 +164,18 @@
     WallpaperColors getWallpaperColors(int which, int userId, int displayId);
 
     /**
+    * @hide
+    */
+    void removeOnLocalColorsChangedListener(
+            in ILocalWallpaperColorConsumer callback, int which, int userId, int displayId);
+
+    /**
+    * @hide
+    */
+    void addOnLocalColorsChangedListener(in ILocalWallpaperColorConsumer callback,
+                                    in List<RectF> regions, int which, int userId, int displayId);
+
+    /**
      * Register a callback to receive color updates from a display
      */
     void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId, int displayId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 49f508d..77daf8d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,7 +22,10 @@
 
 import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.ColorInt;
+import android.annotation.ColorRes;
 import android.annotation.DimenRes;
 import android.annotation.Dimension;
 import android.annotation.DrawableRes;
@@ -33,6 +36,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -403,6 +407,7 @@
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
         STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
+        STANDARD_LAYOUTS.add(R.layout.notification_template_material_call);
         STANDARD_LAYOUTS.add(R.layout.notification_template_header);
     }
 
@@ -649,7 +654,7 @@
     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
-            MessagingStyle.class);
+            MessagingStyle.class, CallStyle.class);
 
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT,
@@ -1203,7 +1208,8 @@
      * of a {@link BigPictureStyle} notification.  This will replace a
      * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided.
      */
-    public static final String EXTRA_PROMOTE_PICTURE = "android.promotePicture";
+    public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED =
+            "android.showBigPictureWhenCollapsed";
 
     /**
      * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
@@ -1318,6 +1324,53 @@
     public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
 
     /**
+     * {@link #extras} key: the type of call represented by the
+     * {@link android.app.Notification.CallStyle} notification. This extra is an int.
+     * @hide
+     */
+    public static final String EXTRA_CALL_TYPE = "android.callType";
+
+    /**
+     * {@link #extras} key: the person to be displayed as calling for the
+     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}.
+     */
+    public static final String EXTRA_CALL_PERSON = "android.callPerson";
+
+    /**
+     * {@link #extras} key: the icon to be displayed as a verification status of the caller on a
+     * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}.
+     */
+    public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon";
+
+    /**
+     * {@link #extras} key: the text to be displayed as a verification status of the caller on a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link CharSequence}.
+     */
+    public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users answers a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_ANSWER_INTENT = "android.answerIntent";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users declines a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_DECLINE_INTENT = "android.declineIntent";
+
+    /**
+     * {@link #extras} key: the intent to be sent when the users hangs up a
+     * {@link android.app.Notification.CallStyle} notification. This extra is a
+     * {@link PendingIntent}.
+     */
+    public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent";
+
+    /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}.
      */
@@ -1551,6 +1604,14 @@
         @SystemApi
         public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11;
 
+        /**
+         * {@code SemanticAction}: Mark content as a potential phishing attempt.
+         * Note that this is only for use by the notification assistant services.
+         * @hide
+         */
+        @SystemApi
+        public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12;
+
         private final Bundle mExtras;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         private Icon mIcon;
@@ -2262,7 +2323,8 @@
                 SEMANTIC_ACTION_THUMBS_UP,
                 SEMANTIC_ACTION_THUMBS_DOWN,
                 SEMANTIC_ACTION_CALL,
-                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
+                SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
+                SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface SemanticAction {}
@@ -5876,11 +5938,11 @@
             return summary;
         }
 
-        private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
+        private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
                 StandardTemplateParams p) {
             final boolean tombstone = (action.actionIntent == null);
             RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
-                    emphazisedMode ? getEmphasizedActionLayoutResource()
+                    emphasizedMode ? getEmphasizedActionLayoutResource()
                             : tombstone ? getActionTombstoneLayoutResource()
                                     : getActionLayoutResource());
             if (!tombstone) {
@@ -5890,43 +5952,42 @@
             if (action.mRemoteInputs != null) {
                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
             }
-            if (emphazisedMode) {
+            if (emphasizedMode) {
                 // change the background bgColor
                 CharSequence title = action.title;
-                ColorStateList[] outResultColor = null;
+                ColorStateList[] outResultColor = new ColorStateList[1];
                 int background = resolveBackgroundColor(p);
                 if (isLegacy()) {
                     title = ContrastColorUtil.clearColorSpans(title);
                 } else {
-                    outResultColor = new ColorStateList[1];
                     title = ensureColorSpanContrast(title, background, outResultColor);
                 }
                 button.setTextViewText(R.id.action0, processTextSpans(title));
-                setTextViewColorPrimary(button, R.id.action0, p);
-                int rippleColor;
-                boolean hasColorOverride = outResultColor != null && outResultColor[0] != null;
+                int textColor = getPrimaryTextColor(p);
+                boolean hasColorOverride = outResultColor[0] != null;
                 if (hasColorOverride) {
                     // There's a span spanning the full text, let's take it and use it as the
                     // background color
                     background = outResultColor[0].getDefaultColor();
-                    int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+                    textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                             background, mInNightMode);
-                    button.setTextColor(R.id.action0, textColor);
-                    rippleColor = textColor;
                 } else if (getRawColor(p) != COLOR_DEFAULT && !isColorized(p)
                         && mTintActionButtons && !mInNightMode) {
-                    rippleColor = resolveContrastColor(p);
-                    button.setTextColor(R.id.action0, rippleColor);
-                } else {
-                    rippleColor = getPrimaryTextColor(p);
+                    textColor = resolveContrastColor(p);
                 }
+                button.setTextColor(R.id.action0, textColor);
                 // We only want about 20% alpha for the ripple
-                rippleColor = (rippleColor & 0x00ffffff) | 0x33000000;
+                final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
                 button.setColorStateList(R.id.action0, "setRippleColor",
                         ColorStateList.valueOf(rippleColor));
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(background));
                 button.setBoolean(R.id.action0, "setHasStroke", !hasColorOverride);
+                if (p.mAllowActionIcons) {
+                    button.setImageViewIcon(R.id.action0, action.getIcon());
+                    boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY);
+                    button.setBoolean(R.id.action0, "setWrapModePriority", priority);
+                }
             } else {
                 button.setTextViewText(R.id.action0, processTextSpans(
                         processLegacyText(action.title)));
@@ -5936,8 +5997,12 @@
                     button.setTextColor(R.id.action0, resolveContrastColor(p));
                 }
             }
-            button.setIntTag(R.id.action0, R.id.notification_action_index_tag,
-                    mActions.indexOf(action));
+            // CallStyle notifications add action buttons which don't actually exist in mActions,
+            //  so we have to omit the index in that case.
+            int actionIndex = mActions.indexOf(action);
+            if (actionIndex != -1) {
+                button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex);
+            }
             return button;
         }
 
@@ -6371,6 +6436,10 @@
             return R.layout.notification_template_material_conversation;
         }
 
+        private int getCallLayoutResource() {
+            return R.layout.notification_template_material_call;
+        }
+
         private int getActionLayoutResource() {
             return R.layout.notification_material_action;
         }
@@ -7000,7 +7069,7 @@
         private Icon mBigLargeIcon;
         private boolean mBigLargeIconSet = false;
         private CharSequence mPictureContentDescription;
-        private boolean mPromotePicture;
+        private boolean mShowBigPictureWhenCollapsed;
 
         public BigPictureStyle() {
         }
@@ -7065,7 +7134,7 @@
          */
         @NonNull
         public BigPictureStyle showBigPictureWhenCollapsed(boolean show) {
-            mPromotePicture = show;
+            mShowBigPictureWhenCollapsed = show;
             return this;
         }
 
@@ -7136,7 +7205,7 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            if (mPicture == null || !mPromotePicture) {
+            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeContentView(increasedHeight);
             }
 
@@ -7166,7 +7235,7 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            if (mPicture == null || !mPromotePicture) {
+            if (mPicture == null || !mShowBigPictureWhenCollapsed) {
                 return super.makeHeadsUpContentView(increasedHeight);
             }
 
@@ -7250,7 +7319,7 @@
                 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION,
                         mPictureContentDescription);
             }
-            extras.putBoolean(EXTRA_PROMOTE_PICTURE, mPromotePicture);
+            extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed);
             extras.putParcelable(EXTRA_PICTURE, mPicture);
         }
 
@@ -7271,7 +7340,7 @@
                         extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION);
             }
 
-            mPromotePicture = extras.getBoolean(EXTRA_PROMOTE_PICTURE);
+            mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED);
             mPicture = extras.getParcelable(EXTRA_PICTURE);
         }
 
@@ -8039,6 +8108,10 @@
                             : mBuilder.getMessagingLayoutResource(),
                     p,
                     bindResult);
+            if (isConversationLayout) {
+                mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+                mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+            }
 
             addExtras(mBuilder.mN.extras);
             if (!isConversationLayout) {
@@ -8925,6 +8998,441 @@
         }
     }
 
+
+
+    /**
+     * Helper class for generating large-format notifications that include a large image attachment.
+     *
+     * Here's how you'd set the <code>CallStyle</code> on a notification:
+     * <pre class="prettyprint">
+     * Notification notif = new Notification.Builder(mContext)
+     *     .setSmallIcon(R.drawable.new_post)
+     *     .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent))
+     *     .build();
+     * </pre>
+     */
+    public static class CallStyle extends Style {
+        private static final int CALL_TYPE_INCOMING = 1;
+        private static final int CALL_TYPE_ONGOING = 2;
+        private static final int CALL_TYPE_SCREENING = 3;
+
+        /**
+         * This is a key used privately on the action.extras to give spacing priority
+         * to the required call actions
+         */
+        private static final String KEY_ACTION_PRIORITY = "key_action_priority";
+
+        private int mCallType;
+        private Person mPerson;
+        private PendingIntent mAnswerIntent;
+        private PendingIntent mDeclineIntent;
+        private PendingIntent mHangUpIntent;
+        private Icon mVerificationIcon;
+        private CharSequence mVerificationText;
+
+        CallStyle() {
+        }
+
+        /**
+         * Create a CallStyle for an incoming call.
+         * This notification will have a decline and an answer action, will allow a single
+         * custom {@link Builder#addAction(Action) action}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for an incoming call.
+         *
+         * @param person        The person displayed as the caller.
+         *                      The person also needs to have a non-empty name associated with it.
+         * @param declineIntent The intent to be sent when the user taps the decline action
+         * @param answerIntent  The intent to be sent when the user taps the answer action
+         */
+        @NonNull
+        public static CallStyle forIncomingCall(@NonNull Person person,
+                @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) {
+            return new CallStyle(CALL_TYPE_INCOMING, person,
+                    null /* hangUpIntent */,
+                    requireNonNull(declineIntent, "declineIntent is required"),
+                    requireNonNull(answerIntent, "answerIntent is required")
+            );
+        }
+
+        /**
+         * Create a CallStyle for an ongoing call.
+         * This notification will have a hang up action, will allow up to two
+         * custom {@link Builder#addAction(Action) actions}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for an ongoing call.
+         *
+         * @param person       The person displayed as being on the other end of the call.
+         *                     The person also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         */
+        @NonNull
+        public static CallStyle forOngoingCall(@NonNull Person person,
+                @NonNull PendingIntent hangUpIntent) {
+            return new CallStyle(CALL_TYPE_ONGOING, person,
+                    requireNonNull(hangUpIntent, "hangUpIntent is required"),
+                    null /* declineIntent */,
+                    null /* answerIntent */
+            );
+        }
+
+        /**
+         * Create a CallStyle for a call that is being screened.
+         * This notification will have a hang up and an answer action, will allow a single
+         * custom {@link Builder#addAction(Action) action}, and will have a default
+         * {@link Builder#setContentText(CharSequence) content text} for a call that is being
+         * screened.
+         *
+         * @param person       The person displayed as the caller.
+         *                     The person also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         * @param answerIntent The intent to be sent when the user taps the answer action
+         */
+        @NonNull
+        public static CallStyle forScreeningCall(@NonNull Person person,
+                @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) {
+            return new CallStyle(CALL_TYPE_SCREENING, person,
+                    requireNonNull(hangUpIntent, "hangUpIntent is required"),
+                    null /* declineIntent */,
+                    requireNonNull(answerIntent, "answerIntent is required")
+            );
+        }
+
+        /**
+         * @param person The person displayed for the incoming call.
+         *             The user also needs to have a non-empty name associated with it.
+         * @param hangUpIntent The intent to be sent when the user taps the hang up action
+         * @param declineIntent The intent to be sent when the user taps the decline action
+         * @param answerIntent The intent to be sent when the user taps the answer action
+         */
+        private CallStyle(int callType, @NonNull Person person,
+                @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent,
+                @Nullable PendingIntent answerIntent) {
+            if (person == null || TextUtils.isEmpty(person.getName())) {
+                throw new IllegalArgumentException("person must have a non-empty a name");
+            }
+            mCallType = callType;
+            mPerson = person;
+            mAnswerIntent = answerIntent;
+            mDeclineIntent = declineIntent;
+            mHangUpIntent = hangUpIntent;
+        }
+
+        /**
+         * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text}
+         * as a verification status of the caller.
+         */
+        @NonNull
+        public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) {
+            mVerificationIcon = verificationIcon;
+            return this;
+        }
+
+        /**
+         * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon}
+         * as a verification status of the caller.
+         */
+        @NonNull
+        public CallStyle setVerificationText(@Nullable CharSequence verificationText) {
+            mVerificationText = safeCharSequence(verificationText);
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean displayCustomViewInline() {
+            // This is a lie; True is returned to make sure that the custom view is not used
+            // instead of the template, but it will not actually be included.
+            return true;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void purgeResources() {
+            super.purgeResources();
+            if (mVerificationIcon != null) {
+                mVerificationIcon.convertToAshmem();
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public void reduceImageSizes(Context context) {
+            super.reduceImageSizes(context);
+            if (mVerificationIcon != null) {
+                int rightIconSize = context.getResources().getDimensionPixelSize(
+                        ActivityManager.isLowRamDeviceStatic()
+                                ? R.dimen.notification_right_icon_size_low_ram
+                                : R.dimen.notification_right_icon_size);
+                mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeContentView(boolean increasedHeight) {
+            return makeCallLayout();
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
+            return makeCallLayout();
+        }
+
+        /**
+         * @hide
+         */
+        public RemoteViews makeBigContentView() {
+            return makeCallLayout();
+        }
+
+        @NonNull
+        private Action makeNegativeAction() {
+            if (mDeclineIntent == null) {
+                return makeAction(R.drawable.ic_call_decline,
+                        R.string.call_notification_hang_up_action,
+                        R.color.call_notification_decline_color, mHangUpIntent);
+            } else {
+                return makeAction(R.drawable.ic_call_decline,
+                        R.string.call_notification_decline_action,
+                        R.color.call_notification_decline_color, mDeclineIntent);
+            }
+        }
+
+        @Nullable
+        private Action makeAnswerAction() {
+            return mAnswerIntent == null ? null : makeAction(R.drawable.ic_call_answer,
+                    R.string.call_notification_answer_action,
+                    R.color.call_notification_answer_color, mAnswerIntent);
+        }
+
+        @NonNull
+        private Action makeAction(@DrawableRes int icon, @StringRes int title,
+                @ColorRes int colorRes, PendingIntent intent) {
+            Action action = new Action.Builder(Icon.createWithResource("", icon),
+                    new SpannableStringBuilder().append(mBuilder.mContext.getString(title),
+                            new ForegroundColorSpan(mBuilder.mContext.getColor(colorRes)),
+                            SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE),
+                    intent).build();
+            action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true);
+            return action;
+        }
+
+        private ArrayList<Action> makeActionsList() {
+            final Action negativeAction = makeNegativeAction();
+            final Action answerAction = makeAnswerAction();
+
+            ArrayList<Action> actions = new ArrayList<>(MAX_ACTION_BUTTONS);
+            final Action lastAction;
+            if (answerAction == null) {
+                // If there's no answer action, put the hang up / decline action at the end
+                lastAction = negativeAction;
+            } else {
+                // Otherwise put the answer action at the end, and put the decline action at start.
+                actions.add(negativeAction);
+                lastAction = answerAction;
+            }
+            // For consistency with the standard actions bar, contextual actions are ignored.
+            for (Action action : Builder.filterOutContextualActions(mBuilder.mActions)) {
+                if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
+                    break;
+                }
+                actions.add(action);
+            }
+            actions.add(lastAction);
+            return actions;
+        }
+
+        private RemoteViews makeCallLayout() {
+            Bundle extras = mBuilder.mN.extras;
+            CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
+            if (text == null) {
+                text = getDefaultText();
+            }
+
+            // Bind standard template
+            StandardTemplateParams p = mBuilder.mParams.reset()
+                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .allowActionIcons(true)
+                    .hideLargeIcon(true)
+                    .text(text)
+                    .summaryText(mBuilder.processLegacyText(mVerificationText));
+            // TODO(b/179178086): hide the snooze button
+            RemoteViews contentView = mBuilder.applyStandardTemplate(
+                    mBuilder.getCallLayoutResource(), p, null /* result */);
+
+            // Bind actions.
+            mBuilder.resetStandardTemplateWithActions(contentView);
+            bindCallActions(contentView, p);
+
+            // Bind some extra conversation-specific header fields.
+            mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
+            mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+            contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+            bindCallerVerification(contentView, p);
+
+            // Bind some custom CallLayout properties
+            contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+                    mBuilder.isColorized(p)
+                            ? mBuilder.getPrimaryTextColor(p)
+                            : mBuilder.resolveContrastColor(p));
+            contentView.setInt(R.id.status_bar_latest_event_content,
+                    "setNotificationBackgroundColor", mBuilder.resolveBackgroundColor(p));
+            contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+                    mBuilder.mN.mLargeIcon);
+            contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+                    mBuilder.mN.extras);
+
+            return contentView;
+        }
+
+        private void bindCallActions(RemoteViews view, StandardTemplateParams p) {
+            view.setViewVisibility(R.id.actions_container, View.VISIBLE);
+            view.setViewVisibility(R.id.actions, View.VISIBLE);
+            view.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+                    RemoteViews.MARGIN_BOTTOM, 0);
+
+            // Clear view padding to allow buttons to start on the left edge.
+            // This must be done before 'setEmphasizedMode' which sets top/bottom margins.
+            view.setViewPadding(R.id.actions, 0, 0, 0, 0);
+            // Add an optional indent that will make buttons start at the correct column when
+            // there is enough space to do so (and fall back to the left edge if not).
+            view.setInt(R.id.actions, "setCollapsibleIndentDimen",
+                    R.dimen.call_notification_collapsible_indent);
+
+            // Emphasize so that buttons have borders or colored backgrounds
+            boolean emphasizedMode = true;
+            view.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode);
+            // Use "wrap_content" (unlike normal emphasized mode) and allow prioritizing the
+            // required actions (Answer, Decline, and Hang Up).
+            view.setBoolean(R.id.actions, "setPrioritizedWrapMode", true);
+
+            // Create the buttons for the generated actions list.
+            int i = 0;
+            for (Action action : makeActionsList()) {
+                final RemoteViews button = mBuilder.generateActionButton(action, emphasizedMode, p);
+                if (i > 0) {
+                    // Clear start margin from non-first buttons to reduce the gap between buttons.
+                    // (8dp remaining gap is from all buttons' standard 4dp inset).
+                    button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0);
+                }
+                view.addView(R.id.actions, button);
+                ++i;
+            }
+        }
+
+        private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) {
+            if (mVerificationIcon != null) {
+                contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon);
+                contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */,
+                        mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP);
+                contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.verification_icon, View.GONE);
+            }
+            if (!TextUtils.isEmpty(mVerificationText)) {
+                contentView.setTextViewText(R.id.verification_text, mVerificationText);
+                mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p);
+                contentView.setViewVisibility(R.id.verification_text, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.verification_text, View.GONE);
+            }
+        }
+
+        @Nullable
+        private String getDefaultText() {
+            switch (mCallType) {
+                case CALL_TYPE_INCOMING:
+                    return mBuilder.mContext.getString(R.string.call_notification_incoming_text);
+                case CALL_TYPE_ONGOING:
+                    return mBuilder.mContext.getString(R.string.call_notification_ongoing_text);
+                case CALL_TYPE_SCREENING:
+                    return mBuilder.mContext.getString(R.string.call_notification_screening_text);
+            }
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        public void addExtras(Bundle extras) {
+            super.addExtras(extras);
+            extras.putInt(EXTRA_CALL_TYPE, mCallType);
+            extras.putParcelable(EXTRA_CALL_PERSON, mPerson);
+            if (mVerificationIcon != null) {
+                extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon);
+            }
+            if (mVerificationText != null) {
+                extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText);
+            }
+            if (mAnswerIntent != null) {
+                extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent);
+            }
+            if (mDeclineIntent != null) {
+                extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent);
+            }
+            if (mHangUpIntent != null) {
+                extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent);
+            }
+            fixTitleAndTextExtras(extras);
+        }
+
+        private void fixTitleAndTextExtras(Bundle extras) {
+            CharSequence sender = mPerson != null ? mPerson.getName() : null;
+            if (sender != null) {
+                extras.putCharSequence(EXTRA_TITLE, sender);
+            }
+            if (extras.getCharSequence(EXTRA_TEXT) == null) {
+                extras.putCharSequence(EXTRA_TEXT, getDefaultText());
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        protected void restoreFromExtras(Bundle extras) {
+            super.restoreFromExtras(extras);
+            mCallType = extras.getInt(EXTRA_CALL_TYPE);
+            mPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+            mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON);
+            mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT);
+            mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT);
+            mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT);
+            mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean hasSummaryInHeader() {
+            return false;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean areNotificationsVisiblyDifferent(Style other) {
+            if (other == null || getClass() != other.getClass()) {
+                return true;
+            }
+            CallStyle otherS = (CallStyle) other;
+            return !Objects.equals(mCallType, otherS.mCallType)
+                    || !Objects.equals(mPerson, otherS.mPerson)
+                    || !Objects.equals(mVerificationText, otherS.mVerificationText);
+        }
+    }
+
     /**
      * Notification style for custom views that are decorated by the system
      *
@@ -9474,6 +9982,15 @@
              * <p>The shortcut activity will be used when the bubble is expanded. This will display
              * the shortcut activity in a floating window over the existing foreground activity.</p>
              *
+             * <p>When the shortcut is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * <p>If the shortcut has not been published when the bubble notification is sent,
              * no bubble will be produced. If the shortcut is deleted while the bubble is active,
              * the bubble will be removed.</p>
@@ -9502,6 +10019,15 @@
              * app content in a floating window over the existing foreground activity. The intent
              * should point to a resizable activity. </p>
              *
+             * <p>When the activity is displayed in a bubble, there will be an intent
+             * extra set on the activity, {@link Intent#EXTRA_IS_BUBBLED}
+             * with {@code true}. You may check this in the onCreate of your activity via:
+             *
+             * <pre class="prettyprint">
+             * boolean isBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+             * </pre>
+             * </p>
+             *
              * @throws NullPointerException if intent is null.
              * @throws NullPointerException if icon is null.
              */
@@ -11376,6 +11902,7 @@
         boolean mHideActions;
         boolean mHideProgress;
         boolean mPromotePicture;
+        boolean mAllowActionIcons;
         CharSequence title;
         CharSequence text;
         CharSequence headerTextSecondary;
@@ -11392,6 +11919,7 @@
             mHideActions = false;
             mHideProgress = false;
             mPromotePicture = false;
+            mAllowActionIcons = false;
             title = null;
             text = null;
             summaryText = null;
@@ -11431,6 +11959,11 @@
             return this;
         }
 
+        final StandardTemplateParams allowActionIcons(boolean allowActionIcons) {
+            this.mAllowActionIcons = allowActionIcons;
+            return this;
+        }
+
         final StandardTemplateParams promotePicture(boolean promotePicture) {
             this.mPromotePicture = promotePicture;
             return this;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 7404e53..c047fc2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -128,11 +128,13 @@
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.IVpnManager;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
 import android.net.NetworkWatchlistManager;
 import android.net.TetheringManager;
+import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -167,9 +169,11 @@
 import android.os.SystemConfigManager;
 import android.os.SystemUpdateManager;
 import android.os.SystemVibrator;
+import android.os.SystemVibratorManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibratorManager;
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
@@ -382,6 +386,15 @@
                         ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE));
             }});
 
+        registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
+                new CachedServiceFetcher<VpnManager>() {
+            @Override
+            public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE);
+                IVpnManager service = IVpnManager.Stub.asInterface(b);
+                return new VpnManager(ctx, service);
+            }});
+
         registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class,
                 new CachedServiceFetcher<VcnManager>() {
             @Override
@@ -699,6 +712,13 @@
                     }
                 });
 
+        registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
+                new CachedServiceFetcher<VibratorManager>() {
+                    @Override
+                    public VibratorManager createService(ContextImpl ctx) {
+                        return new SystemVibratorManager(ctx);
+                    }});
+
         registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
                 new CachedServiceFetcher<Vibrator>() {
             @Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 390d921..9019ddf 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -53,13 +53,6 @@
     public int userId;
 
     /**
-     * The id of the ActivityStack that currently contains this task.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int stackId;
-
-    /**
      * The identifier for this task.
      */
     public int taskId;
@@ -350,7 +343,9 @@
                 // TopActivityToken and bounds are important if top activity is in size compat
                 && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
                 && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
-                    .equals(that.configuration.windowConfiguration.getBounds()));
+                    .equals(that.configuration.windowConfiguration.getBounds()))
+                && (!topActivityInSizeCompat || configuration.getLayoutDirection()
+                    == that.configuration.getLayoutDirection());
     }
 
     /**
@@ -358,7 +353,6 @@
      */
     void readFromParcel(Parcel source) {
         userId = source.readInt();
-        stackId = source.readInt();
         taskId = source.readInt();
         displayId = source.readInt();
         isRunning = source.readBoolean();
@@ -394,7 +388,6 @@
      */
     void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(userId);
-        dest.writeInt(stackId);
         dest.writeInt(taskId);
         dest.writeInt(displayId);
         dest.writeBoolean(isRunning);
@@ -428,7 +421,7 @@
 
     @Override
     public String toString() {
-        return "TaskInfo{userId=" + userId + " stackId=" + stackId + " taskId=" + taskId
+        return "TaskInfo{userId=" + userId + " taskId=" + taskId
                 + " displayId=" + displayId
                 + " isRunning=" + isRunning
                 + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index e1c262c..9b99ab8 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -477,11 +477,13 @@
      * Changes to night mode take effect globally and will result in a configuration change
      * (and potentially an Activity lifecycle event) being applied to all running apps.
      * Developers interested in an app-local implementation of night mode should consider using
-     * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} to manage the
-     * -night qualifier locally.
+     * {@link #setApplicationNightMode(int)} to set and persist the -night qualifier locally or
+     * {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)} for the
+     * backward compatible implementation.
      *
      * @param mode the night mode to set
      * @see #getNightMode()
+     * @see #setApplicationNightMode(int)
      */
     public void setNightMode(@NightMode int mode) {
         if (mService != null) {
@@ -494,6 +496,44 @@
     }
 
     /**
+     * Sets and persist the night mode for this application.
+     * <p>
+     * The mode can be one of:
+     * <ul>
+     *   <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+     *       {@code notnight} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
+     *       {@code night} mode</li>
+     *   <li><em>{@link #MODE_NIGHT_CUSTOM}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the custom time set (or default)</li>
+     *   <li><em>{@link #MODE_NIGHT_AUTO}</em> automatically switches between
+     *       {@code night} and {@code notnight} based on the device's current
+     *       location and certain other sensors</li>
+     * </ul>
+     * <p>
+     * Changes to night mode take effect locally and will result in a configuration change
+     * (and potentially an Activity lifecycle event) being applied to this application. The mode
+     * is persisted for this application until it is either modified by the application, the
+     * user clears the data for the application, or this application is uninstalled.
+     * <p>
+     * Developers interested in a non-persistent app-local implementation of night mode should
+     * consider using {@link android.support.v7.app.AppCompatDelegate#setDefaultNightMode(int)}
+     * to manage the -night qualifier locally.
+     *
+     * @param mode the night mode to set
+     * @see #setNightMode(int)
+     */
+    public void setApplicationNightMode(@NightMode int mode) {
+        if (mService != null) {
+            try {
+                mService.setApplicationNightMode(mode);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Returns the currently configured night mode.
      * <p>
      * May be one of:
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ac2f223..d7587bd 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -66,6 +66,7 @@
 import android.os.StrictMode;
 import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
@@ -107,6 +108,8 @@
     private static boolean DEBUG = false;
     private float mWallpaperXStep = -1;
     private float mWallpaperYStep = -1;
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     /** {@hide} */
     private static final String PROP_WALLPAPER = "ro.config.wallpaper";
@@ -309,6 +312,8 @@
         private int mCachedWallpaperUserId;
         private Bitmap mDefaultWallpaper;
         private Handler mMainLooperHandler;
+        private ArrayMap<LocalWallpaperColorConsumer, ILocalWallpaperColorConsumer>
+                mLocalColorCallbacks = new ArrayMap<>();
 
         Globals(IWallpaperManager service, Looper looper) {
             mService = service;
@@ -350,6 +355,40 @@
             }
         }
 
+        private ILocalWallpaperColorConsumer wrap(LocalWallpaperColorConsumer callback) {
+            ILocalWallpaperColorConsumer callback2 = new ILocalWallpaperColorConsumer.Stub() {
+                @Override
+                public void onColorsChanged(RectF area, WallpaperColors colors) {
+                    callback.onColorsChanged(area, colors);
+                }
+            };
+            mLocalColorCallbacks.put(callback, callback2);
+            return callback2;
+        }
+
+        public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+                @NonNull List<RectF> regions, int which, int userId, int displayId) {
+            try {
+                mService.addOnLocalColorsChangedListener(wrap(callback) , regions, which,
+                                                         userId, displayId);
+            } catch (RemoteException e) {
+                // Can't get colors, connection lost.
+            }
+        }
+
+        public void removeOnColorsChangedListener(
+                @NonNull LocalWallpaperColorConsumer callback, int which, int userId,
+                int displayId) {
+            ILocalWallpaperColorConsumer callback2 = mLocalColorCallbacks.remove(callback);
+            if (callback2 == null) return;
+            try {
+                mService.removeOnLocalColorsChangedListener(
+                        callback2, which, userId, displayId);
+            } catch (RemoteException e) {
+                // Can't get colors, connection lost.
+            }
+        }
+
         /**
          * Stop listening to wallpaper color events.
          *
@@ -1057,6 +1096,29 @@
     }
 
     /**
+     * @hide
+     */
+    public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+            List<RectF> regions) throws IllegalArgumentException {
+        for (RectF region : regions) {
+            if (!LOCAL_COLOR_BOUNDS.contains(region)) {
+                throw new IllegalArgumentException("Regions must be within bounds "
+                        + LOCAL_COLOR_BOUNDS);
+            }
+        }
+        sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM,
+                                                 mContext.getUserId(), mContext.getDisplayId());
+    }
+
+    /**
+     * @hide
+     */
+    public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+        sGlobals.removeOnColorsChangedListener(callback, FLAG_SYSTEM, mContext.getUserId(),
+                mContext.getDisplayId());
+    }
+
+    /**
      * Version of {@link #getWallpaperFile(int)} that can access the wallpaper data
      * for a given user.  The caller must hold the INTERACT_ACROSS_USERS_FULL
      * permission to access another user's wallpaper data.
@@ -2202,4 +2264,18 @@
             onColorsChanged(colors, which);
         }
     }
+
+    /**
+     * Callback to update a consumer with a local color change
+     * @hide
+     */
+    public interface LocalWallpaperColorConsumer {
+
+        /**
+         * Gets called when a color of an area gets updated
+         * @param area
+         * @param colors
+         */
+        void onColorsChanged(RectF area, WallpaperColors colors);
+    }
 }
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d175a66..4dbff0c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -16,6 +16,8 @@
 
 package android.app.admin;
 
+import static android.app.admin.DevicePolicyManager.OperationSafetyReason;
+
 import android.accounts.AccountManager;
 import android.annotation.BroadcastBehavior;
 import android.annotation.IntDef;
@@ -35,6 +37,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.security.KeyChain;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -72,8 +75,8 @@
  * </div>
  */
 public class DeviceAdminReceiver extends BroadcastReceiver {
-    private static String TAG = "DevicePolicy";
-    private static boolean localLOGV = false;
+    private static final String TAG = "DevicePolicy";
+    private static final boolean LOCAL_LOGV = false;
 
     /**
      * This is the primary action that a device administrator must implement to be
@@ -509,6 +512,36 @@
     public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
             "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
 
+    /**
+     * Broadcast action: notify the admin that the state of operations that can be unsafe because
+     * of a given reason (specified by the {@link #EXTRA_OPERATION_SAFETY_REASON} {@code int} extra)
+     * has changed (the new value is specified by the {@link #EXTRA_OPERATION_SAFETY_STATE}
+     * {@code boolean} extra).
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_OPERATION_SAFETY_STATE_CHANGED =
+            "android.app.action.OPERATION_SAFETY_STATE_CHANGED";
+
+    /**
+     * An {@code int} extra specifying an {@link OperationSafetyReason}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_OPERATION_SAFETY_REASON  =
+            "android.app.extra.OPERATION_SAFETY_REASON";
+
+    /**
+     * An {@code boolean} extra specifying whether an operation will fail due to a
+     * {@link OperationSafetyReason}. {@code true} means operations that rely on that reason are
+     * safe, while {@code false} means they're unsafe.
+     *
+     * @hide
+     */
+    public static final String EXTRA_OPERATION_SAFETY_STATE  =
+            "android.app.extra.OPERATION_SAFETY_STATE";
+
     private DevicePolicyManager mManager;
     private ComponentName mWho;
 
@@ -1018,6 +1051,51 @@
     }
 
     /**
+     * Called to notify the state of operations that can be unsafe to execute has changed.
+     *
+     * <p><b>Note:/b> notice that the operation safety state might change between the time this
+     * callback is received and the operation's method on {@link DevicePolicyManager} is called, so
+     * calls to the latter could still throw a {@link UnsafeStateException} even when this method
+     * is called with {@code isSafe} as {@code true}
+     *
+     * @param context the running context as per {@link #onReceive}
+     * @param reason the reason an operation could be unsafe.
+     * @param isSafe whether the operation is safe to be executed.
+     */
+    public void onOperationSafetyStateChanged(@NonNull Context context,
+            @OperationSafetyReason int reason, boolean isSafe) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, String.format("onOperationSafetyStateChanged(): %s=%b",
+                    DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+        }
+    }
+
+    private void onOperationSafetyStateChanged(Context context, Intent intent) {
+        if (!hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_REASON)
+                || !hasRequiredExtra(intent, EXTRA_OPERATION_SAFETY_STATE)) {
+            return;
+        }
+
+        int reason = intent.getIntExtra(EXTRA_OPERATION_SAFETY_REASON,
+                DevicePolicyManager.OPERATION_SAFETY_REASON_NONE);
+        if (!DevicePolicyManager.isValidOperationSafetyReason(reason)) {
+            Log.wtf(TAG, "Received invalid reason on " + intent.getAction() + ": " + reason);
+            return;
+        }
+        boolean isSafe = intent.getBooleanExtra(EXTRA_OPERATION_SAFETY_STATE,
+                /* defaultValue=*/ false);
+
+        onOperationSafetyStateChanged(context, reason, isSafe);
+    }
+
+    private boolean hasRequiredExtra(Intent intent, String extra) {
+        if (intent.hasExtra(extra)) return true;
+
+        Log.wtf(TAG, "Missing '" + extra + "' on intent " +  intent);
+        return false;
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -1025,6 +1103,9 @@
     @Override
     public void onReceive(@NonNull Context context, @NonNull Intent intent) {
         String action = intent.getAction();
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "onReceive(): received " + action + " on user " + context.getUserId());
+        }
 
         if (ACTION_PASSWORD_CHANGED.equals(action)) {
             onPasswordChanged(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
@@ -1092,6 +1173,8 @@
         } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
             onTransferAffiliatedProfileOwnershipComplete(context,
                     intent.getParcelableExtra(Intent.EXTRA_USER));
+        } else if (ACTION_OPERATION_SAFETY_STATE_CHANGED.equals(action)) {
+            onOperationSafetyStateChanged(context, intent);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff41d1c8..05e9dcf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -206,7 +206,6 @@
      * {@link android.os.Build.VERSION_CODES#N}</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
@@ -250,7 +249,6 @@
      * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * </ul>
      *
      * <p>If provisioning fails, the device returns to its previous state.
@@ -289,7 +287,6 @@
      * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li>
      * </ul>
@@ -388,8 +385,6 @@
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOCAL_TIME} (convert to String), optional</li>
      * <li>{@link #EXTRA_PROVISIONING_TIME_ZONE}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LOCALE}, optional</li>
@@ -438,8 +433,6 @@
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
@@ -654,7 +647,10 @@
      *
      * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} or
      * {@link #ACTION_PROVISION_MANAGED_DEVICE}.
+     *
+     * @deprecated Color customization is no longer supported in the provisioning flow.
      */
+    @Deprecated
     public static final String EXTRA_PROVISIONING_MAIN_COLOR =
              "android.app.extra.PROVISIONING_MAIN_COLOR";
 
@@ -905,8 +901,10 @@
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
      * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
+     * @deprecated This extra is no longer respected in the provisioning flow.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL =
             "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -930,9 +928,11 @@
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
      * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
+     * @deprecated This extra is no longer respected in the provisioning flow.
      * @hide
      */
     @SystemApi
+    @Deprecated
     public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI =
             "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
 
@@ -1300,10 +1300,11 @@
      * A value for {@link #EXTRA_PROVISIONING_SUPPORTED_MODES} indicating that provisioning is
      * organization-owned.
      *
-     * <p>Using this value will cause the admin app's {@link #ACTION_GET_PROVISIONING_MODE}
-     * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
-     * contain {@link #PROVISIONING_MODE_MANAGED_PROFILE} and {@link
-     * #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
+     * <p>Using this value indicates the admin app can only be provisioned in either a
+     * fully-managed device or a corporate-owned work profile. This will cause the admin app's
+     * {@link #ACTION_GET_PROVISIONING_MODE} activity to have the {@link
+     * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
+     * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
      *
      * <p>Also, if this value is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
      * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
@@ -2922,33 +2923,61 @@
         return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
     }
 
-    private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+    private static final String PREFIX_OPERATION_SAFETY_REASON = "OPERATION_SAFETY_REASON_";
 
     /** @hide */
-    @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
-            UNSAFE_OPERATION_REASON_NONE,
-            UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+    @IntDef(prefix = PREFIX_OPERATION_SAFETY_REASON, value = {
+            OPERATION_SAFETY_REASON_NONE,
+            OPERATION_SAFETY_REASON_DRIVING_DISTRACTION
     })
     @Retention(RetentionPolicy.SOURCE)
-    public static @interface UnsafeOperationReason {
+    public static @interface OperationSafetyReason {
     }
 
     /** @hide */
     @TestApi
-    public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+    public static final int OPERATION_SAFETY_REASON_NONE = -1;
 
     /**
      * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
      * the driver of the vehicle.
      */
-    public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+    public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
 
     /** @hide */
     @NonNull
     @TestApi
-    public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+    public static String operationSafetyReasonToString(@OperationSafetyReason int reason) {
         return DebugUtils.constantToString(DevicePolicyManager.class,
-                PREFIX_UNSAFE_OPERATION_REASON, reason);
+                PREFIX_OPERATION_SAFETY_REASON, reason);
+    }
+
+    /** @hide */
+    public static boolean isValidOperationSafetyReason(@OperationSafetyReason int reason) {
+        return reason == OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+    }
+
+    /**
+     * Checks if it's safe to run operations that can be affected by the given {@code reason}.
+     *
+     * <p><b>Note:/b> notice that the operation safety state might change between the time this
+     * method returns and the operation's method is called, so calls to the latter could still throw
+     * a {@link UnsafeStateException} even when this method returns {@code true}.
+     *
+     * @param reason currently, only supported reason is
+     * {@link #OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
+     *
+     * @return whether it's safe to run operations that can be affected by the given {@code reason}.
+     */
+    // TODO(b/173541467): should it throw SecurityException if caller is not admin?
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        if (mService == null) return false;
+
+        try {
+            return mService.isSafeOperation(reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /** @hide */
@@ -11689,8 +11718,11 @@
     }
 
     /**
-     * Called by a device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to
-     * control the network logging feature.
+     * Called by a device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
      *
      * <p> Network logs contain DNS lookup and connect() library call events. The following library
      *     functions are recorded while network logging is active:
@@ -11730,7 +11762,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
      * @param enabled whether network logging should be enabled or not.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner.
      * @see #setAffiliationIds
      * @see #retrieveNetworkLogs
      */
@@ -11744,14 +11776,16 @@
     }
 
     /**
-     * Return whether network logging is enabled by a device owner.
+     * Return whether network logging is enabled by a device owner or profile owner of
+     * a managed profile.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
      * be {@code null} if the caller is a delegated app with {@link #DELEGATION_NETWORK_LOGGING}
      * or has MANAGE_USERS permission.
-     * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner and caller has
-     * no MANAGE_USERS permission
+     * @return {@code true} if network logging is enabled by device owner or profile owner,
+     * {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner or profile owner and
+     * caller has no MANAGE_USERS permission
      */
     public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isNetworkLoggingEnabled");
@@ -11763,9 +11797,14 @@
     }
 
     /**
-     * Called by device owner or delegated app with {@link #DELEGATION_NETWORK_LOGGING} to retrieve
-     * the most recent batch of network logging events.
-     * A device owner has to provide a batchToken provided as part of
+     * Called by device owner, profile owner of a managed profile or delegated app with
+     * {@link #DELEGATION_NETWORK_LOGGING} to retrieve the most recent batch of
+     * network logging events.
+     *
+     * <p> When network logging is enabled by a profile owner, the network logs will only include
+     * work profile network activity, not activity on the personal profile.
+     *
+     * A device owner or profile owner has to provide a batchToken provided as part of
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
      * token of the most recent available batch of logs, {@code null} will be returned.
      *
@@ -11777,11 +11816,11 @@
      * after the device device owner has been notified via
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
      *
-     * <p>If a secondary user or profile is created, calling this method will throw a
-     * {@link SecurityException} until all users become affiliated again. It will also no longer be
-     * possible to retrieve the network logs batch with the most recent batchToken provided
-     * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
-     * {@link DevicePolicyManager#setAffiliationIds}.
+     * <p>If the caller is not a profile owner and a secondary user or profile is created, calling
+     * this method will throw a {@link SecurityException} until all users become affiliated again.
+     * It will also no longer be possible to retrieve the network logs batch with the most recent
+     * batchToken provided by {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
+     * See {@link DevicePolicyManager#setAffiliationIds}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *        {@code null} if called by a delegated app.
@@ -11789,8 +11828,9 @@
      * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
      *        {@code null} if the batch represented by batchToken is no longer available or if
      *        logging is disabled.
-     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
-     * profile or secondary user that is not affiliated with the device.
+     * @throws SecurityException if {@code admin} is not a device owner, profile owner or if the
+     * {@code admin} is not a profile owner and there is at least one profile or secondary user
+     * that is not affiliated with the device.
      * @see #setAffiliationIds
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
      */
@@ -11909,11 +11949,12 @@
     }
 
     /**
-     * Called by the system to get the time at which the device owner last retrieved network logging
-     * events.
+     * Called by the system to get the time at which the device owner or profile owner of a
+     * managed profile last retrieved network logging events.
      *
-     * @return the time at which the device owner most recently retrieved network logging events, in
-     *         milliseconds since epoch; -1 if network logging events were never retrieved.
+     * @return the time at which the device owner or profile owner most recently retrieved network
+     *         logging events, in milliseconds since epoch; -1 if network logging events were
+     *         never retrieved.
      * @throws SecurityException if the caller is not the device owner, does not hold the
      *         MANAGE_USERS permission and is not the system.
      *
@@ -13157,7 +13198,7 @@
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
     public void setNextOperationSafety(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         if (mService != null) {
             try {
                 mService.setNextOperationSafety(operation, reason);
@@ -13329,26 +13370,93 @@
      */
     public boolean canAdminGrantSensorsPermissions() {
         throwIfParentInstance("canAdminGrantSensorsPermissions");
-        return canAdminGrantSensorsPermissionsForUser(myUserId());
-    }
-
-    /**
-     * Returns true if the admin can control grants of sensors-related permissions, for
-     * a given user.
-     *
-     * @hide
-     * @param userId The ID of the user to check.
-     * @return if the admin may grant these permissions, false otherwise.
-     */
-    @SystemApi
-    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
         if (mService == null) {
             return false;
         }
         try {
-            return mService.canAdminGrantSensorsPermissionsForUser(userId);
+            return mService.canAdminGrantSensorsPermissionsForUser(myUserId());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to
+     * enable or disable USB data signaling for the device. When disabled, USB data connections
+     * (except from charging functions) are prohibited.
+     *
+     * <p> This API is not supported on all devices, the caller should call
+     * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
+     * signaling is supported on the device.
+     *
+     * @param enabled whether USB data signaling should be enabled or not.
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     *         an organization-owned managed profile.
+     * @throws IllegalStateException if disabling USB data signaling is not supported or
+     *         if USB data signaling fails to be enabled/disabled.
+     */
+    public void setUsbDataSignalingEnabled(boolean enabled) {
+        throwIfParentInstance("setUsbDataSignalingEnabled");
+        if (mService != null) {
+            try {
+                mService.setUsbDataSignalingEnabled(mContext.getPackageName(), enabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to return
+     * whether USB data signaling is currently enabled by the admin.
+     *
+     * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+     */
+    public boolean isUsbDataSignalingEnabled() {
+        throwIfParentInstance("isUsbDataSignalingEnabled");
+        if (mService != null) {
+            try {
+                return mService.isUsbDataSignalingEnabled(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called by the system to check whether USB data signaling is currently enabled for this user.
+     *
+     * @param userId which user to check for.
+     * @return {@code true} if USB data signaling is enabled, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) {
+        throwIfParentInstance("isUsbDataSignalingEnabledForUser");
+        if (mService != null) {
+            try {
+                return mService.isUsbDataSignalingEnabledForUser(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether enabling or disabling USB data signaling is supported on the device.
+     *
+     * @return {@code true} if the device supports enabling and disabling USB data signaling.
+     */
+    public boolean canUsbDataSignalingBeDisabled() {
+        throwIfParentInstance("canUsbDataSignalingBeDisabled");
+        if (mService != null) {
+            try {
+                return mService.canUsbDataSignalingBeDisabled();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index a0d2977..67f5c36 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.UserHandle;
@@ -255,4 +256,14 @@
      * {@link #supportsResetOp(int)} is true.
      */
     public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+    /**
+     * Notifies the system that an unsafe operation reason has changed.
+     *
+     * @throws IllegalArgumentException if {@code checker} is not the same as set on
+     *         {@code DevicePolicyManagerService}.
+     */
+    public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
+            @OperationSafetyReason int reason, boolean isSafe);
+
 }
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index 6c6f2aa..17b74b1 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,7 +17,7 @@
 
 import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -31,15 +31,20 @@
     /**
      * Returns whether the given {@code operation} can be safely executed at the moment.
      */
-    @UnsafeOperationReason
+    @OperationSafetyReason
     int getUnsafeOperationReason(@DevicePolicyOperation int operation);
 
     /**
+     * Return whether it's safe to run operations that can be affected by the given {@code reason}.
+     */
+    boolean isSafeOperation(@OperationSafetyReason int reason);
+
+    /**
      * Returns a new exception for when the given {@code operation} cannot be safely executed.
      */
     @NonNull
     default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         return new UnsafeStateException(operation, reason);
     }
 
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 5e1cbad..9eb9a7b 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
 
 import java.util.Locale;
 
@@ -35,6 +36,13 @@
  */
 @TestApi
 public final class FullyManagedDeviceProvisioningParams implements Parcelable {
+    private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+            "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+    private static final String CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM =
+            "CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS";
+    private static final String TIME_ZONE_PROVIDED_PARAM = "TIME_ZONE_PROVIDED";
+    private static final String LOCALE_PROVIDED_PARAM = "LOCALE_PROVIDED";
+
     @NonNull private final ComponentName mDeviceAdminComponentName;
     @NonNull private final String mOwnerName;
     private final boolean mLeaveAllSystemAppsEnabled;
@@ -121,6 +129,29 @@
     }
 
     /**
+     * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     */
+    public void logParams(@NonNull String callerPackage) {
+        requireNonNull(callerPackage);
+
+        logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+        logParam(callerPackage, CAN_DEVICE_OWNER_GRANT_SENSOR_PERMISSIONS_PARAM,
+                mDeviceOwnerCanGrantSensorsPermissions);
+        logParam(callerPackage, TIME_ZONE_PROVIDED_PARAM, /* value= */ mTimeZone != null);
+        logParam(callerPackage, LOCALE_PROVIDED_PARAM, /* value= */ mLocale != null);
+    }
+
+    private void logParam(String callerPackage, String param, boolean value) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+                .setStrings(callerPackage)
+                .setAdmin(mDeviceAdminComponentName)
+                .setStrings(param)
+                .setBoolean(value)
+                .write();
+    }
+
+    /**
      * Builder class for {@link FullyManagedDeviceProvisioningParams} objects.
      */
     public static final class Builder {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 89f30cc..94388cf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -492,6 +492,7 @@
     boolean canProfileOwnerResetPasswordWhenLocked(int userId);
 
     void setNextOperationSafety(int operation, int reason);
+    boolean isSafeOperation(int reason);
 
     String getEnrollmentSpecificId(String callerPackage);
     void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
@@ -501,4 +502,9 @@
 
     void resetDefaultCrossProfileIntentFilters(int userId);
     boolean canAdminGrantSensorsPermissionsForUser(int userId);
+
+    void setUsbDataSignalingEnabled(String callerPackage, boolean enabled);
+    boolean isUsbDataSignalingEnabled(String callerPackage);
+    boolean isUsbDataSignalingEnabledForUser(int userId);
+    boolean canUsbDataSignalingBeDisabled();
 }
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index 5fe63d1..1a6099a 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.stats.devicepolicy.DevicePolicyEnums;
 
 /**
  * Params required to provision a managed profile, see
@@ -34,6 +35,13 @@
  */
 @TestApi
 public final class ManagedProfileProvisioningParams implements Parcelable {
+    private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
+            "LEAVE_ALL_SYSTEM_APPS_ENABLED";
+    private static final String ORGANIZATION_OWNED_PROVISIONING_PARAM =
+            "ORGANIZATION_OWNED_PROVISIONING";
+    private static final String ACCOUNT_TO_MIGRATE_PROVIDED_PARAM = "ACCOUNT_TO_MIGRATE_PROVIDED";
+    private static final String KEEP_MIGRATED_ACCOUNT_PARAM = "KEEP_MIGRATED_ACCOUNT";
+
     @NonNull private final ComponentName mProfileAdminComponentName;
     @NonNull private final String mOwnerName;
     @Nullable private final String mProfileName;
@@ -93,6 +101,30 @@
     }
 
     /**
+     * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     */
+    public void logParams(@NonNull String callerPackage) {
+        requireNonNull(callerPackage);
+
+        logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
+        logParam(callerPackage, ORGANIZATION_OWNED_PROVISIONING_PARAM,
+                mOrganizationOwnedProvisioning);
+        logParam(callerPackage, KEEP_MIGRATED_ACCOUNT_PARAM, mKeepAccountMigrated);
+        logParam(callerPackage, ACCOUNT_TO_MIGRATE_PROVIDED_PARAM,
+                /* value= */ mAccountToMigrate != null);
+    }
+
+    private void logParam(String callerPackage, String param, boolean value) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_PARAM)
+                .setStrings(callerPackage)
+                .setAdmin(mProfileAdminComponentName)
+                .setStrings(param)
+                .setBoolean(value)
+                .write();
+    }
+
+    /**
      * Builder class for {@link ManagedProfileProvisioningParams} objects.
      */
     public static final class Builder {
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 56eeb06..f1f6526 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,18 +15,20 @@
  */
 package android.app.admin;
 
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+import static android.app.admin.DevicePolicyManager.isValidOperationSafetyReason;
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
  * was not safe to be executed at that moment.
@@ -39,17 +41,15 @@
 public final class UnsafeStateException extends IllegalStateException implements Parcelable {
 
     private final @DevicePolicyOperation int mOperation;
-    private final @UnsafeOperationReason int mReason;
+    private final @OperationSafetyReason int mReason;
 
     /** @hide */
     @TestApi
     public UnsafeStateException(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         super();
-        Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
-                "invalid reason %d, must be %d (%s)", reason,
-                UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
-                unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
+        Preconditions.checkArgument(isValidOperationSafetyReason(reason), "invalid reason %d",
+                reason);
         mOperation = operation;
         mReason = reason;
     }
@@ -61,19 +61,20 @@
     }
 
     /**
-     * Gets the reason the operation is unsafe.
+     * Gets the reasons the operation is unsafe.
      *
      * @return currently, only valid reason is
-     * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+     * {@link android.app.admin.DevicePolicyManager#OPERATION_SAFETY_REASON_DRIVING_DISTRACTION}.
      */
-    public @UnsafeOperationReason int getReason() {
-        return mReason;
+    @NonNull
+    public List<Integer> getReasons() {
+        return Arrays.asList(mReason);
     }
 
     /** @hide */
     @Override
     public String getMessage() {
-        return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+        return DevicePolicyManager.operationSafetyReasonToString(mReason);
     }
 
     @Override
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 7e232ac..85cfe83 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -401,7 +401,8 @@
      * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
      */
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
-        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this);
+        FullBackup.BackupScheme backupScheme = FullBackup.getBackupScheme(this,
+                mOperationType);
         if (!isDeviceToDeviceMigration() && !backupScheme.isFullBackupContentEnabled()) {
             return;
         }
@@ -624,7 +625,8 @@
         if (includeMap == null || includeMap.size() == 0) {
             // Do entire sub-tree for the provided token.
             fullBackupFileTree(packageName, domainToken,
-                    FullBackup.getBackupScheme(this).tokenToDirectoryPath(domainToken),
+                    FullBackup.getBackupScheme(this, mOperationType)
+                            .tokenToDirectoryPath(domainToken),
                     filterSet, traversalExcludeSet, data);
         } else if (includeMap.get(domainToken) != null) {
             // This will be null if the xml parsing didn't yield any rules for
@@ -795,7 +797,8 @@
                                             ArraySet<String> systemExcludes,
             FullBackupDataOutput output) {
         // Pull out the domain and set it aside to use when making the tarball.
-        String domainPath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+        String domainPath = FullBackup.getBackupScheme(this, mOperationType)
+                .tokenToDirectoryPath(domain);
         if (domainPath == null) {
             // Should never happen.
             return;
@@ -911,7 +914,7 @@
             return true;
         }
 
-        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this);
+        FullBackup.BackupScheme bs = FullBackup.getBackupScheme(this, mOperationType);
         if (!bs.isFullBackupContentEnabled()) {
             if (Log.isLoggable(FullBackup.TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(FullBackup.TAG_XML_PARSER,
@@ -985,7 +988,8 @@
                 + " domain=" + domain + " relpath=" + path + " mode=" + mode
                 + " mtime=" + mtime);
 
-        basePath = FullBackup.getBackupScheme(this).tokenToDirectoryPath(domain);
+        basePath = FullBackup.getBackupScheme(this, mOperationType).tokenToDirectoryPath(
+                domain);
         if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
             mode = -1;  // < 0 is a token to skip attempting a chmod()
         }
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 742d05c..f7ed6f1f 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,6 +16,9 @@
 
 package android.app.backup;
 
+import static android.app.backup.BackupManager.OperationType;
+
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -41,6 +44,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -90,27 +94,61 @@
             "fakeClientSideEncryption";
 
     /**
+     * Identify {@link BackupScheme} object by package and operation type
+     * (see {@link OperationType}) it corresponds to.
+     */
+    private static class BackupSchemeId {
+        final String mPackageName;
+        @OperationType final int mOperationType;
+
+        BackupSchemeId(String packageName, @OperationType int operationType) {
+            mPackageName = packageName;
+            mOperationType = operationType;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mOperationType);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object == null || getClass() != object.getClass()) {
+                return false;
+            }
+            BackupSchemeId that = (BackupSchemeId) object;
+            return Objects.equals(mPackageName, that.mPackageName) &&
+                    Objects.equals(mOperationType, that.mOperationType);
+        }
+    }
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
     static public native int backupToTar(String packageName, String domain,
             String linkdomain, String rootpath, String path, FullBackupDataOutput output);
 
-    private static final Map<String, BackupScheme> kPackageBackupSchemeMap =
-            new ArrayMap<String, BackupScheme>();
+    private static final Map<BackupSchemeId, BackupScheme> kPackageBackupSchemeMap =
+            new ArrayMap<>();
 
-    static synchronized BackupScheme getBackupScheme(Context context) {
+    static synchronized BackupScheme getBackupScheme(Context context,
+            @OperationType int operationType) {
+        BackupSchemeId backupSchemeId = new BackupSchemeId(context.getPackageName(), operationType);
         BackupScheme backupSchemeForPackage =
-                kPackageBackupSchemeMap.get(context.getPackageName());
+                kPackageBackupSchemeMap.get(backupSchemeId);
         if (backupSchemeForPackage == null) {
-            backupSchemeForPackage = new BackupScheme(context);
-            kPackageBackupSchemeMap.put(context.getPackageName(), backupSchemeForPackage);
+            backupSchemeForPackage = new BackupScheme(context, operationType);
+            kPackageBackupSchemeMap.put(backupSchemeId, backupSchemeForPackage);
         }
         return backupSchemeForPackage;
     }
 
     public static BackupScheme getBackupSchemeForTest(Context context) {
-        BackupScheme testing = new BackupScheme(context);
+        BackupScheme testing = new BackupScheme(context, OperationType.BACKUP);
         testing.mExcludes = new ArraySet();
         testing.mIncludes = new ArrayMap();
         return testing;
@@ -236,6 +274,7 @@
         private final static String TAG_EXCLUDE = "exclude";
 
         final int mFullBackupContent;
+        @OperationType final int mOperationType;
         final PackageManager mPackageManager;
         final StorageManager mStorageManager;
         final String mPackageName;
@@ -354,8 +393,9 @@
          */
         ArraySet<PathWithRequiredFlags> mExcludes;
 
-        BackupScheme(Context context) {
+        BackupScheme(Context context, @OperationType int operationType) {
             mFullBackupContent = context.getApplicationInfo().fullBackupContent;
+            mOperationType = operationType;
             mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index 28b7340..ab38832 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -20,8 +20,16 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.compat.Compatibility;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.IPlatformCompat;
+
+import java.util.Map;
+
 /**
  * CompatChanges APIs - to be used by platform code only (including mainline
  * modules).
@@ -89,4 +97,25 @@
         return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
     }
 
+    /**
+     * Set an app compat override for a given package. This will check whether the caller is allowed
+     * to perform this operation on the given apk and build. Only the installer package is allowed
+     * to set overrides on a non-debuggable final build and a non-test apk.
+     *
+     * @param packageName The package name of the app in question.
+     * @param overrides A map from changeId to the override applied for this change id.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG)
+    public static void setPackageOverride(String packageName,
+            Map<Long, PackageOverride> overrides) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
+        try {
+            platformCompat.setOverridesFromInstaller(config, packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/compat/OWNERS b/core/java/android/app/compat/OWNERS
new file mode 100644
index 0000000..f8c3520
--- /dev/null
+++ b/core/java/android/app/compat/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/compat/OWNERS
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
new file mode 100644
index 0000000..9f97cd4
--- /dev/null
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -0,0 +1,211 @@
+/*
+ * 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.app.compat;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An app compat override applied to a given package and change id pairing.
+ *
+ * A package override contains a list of version ranges with the desired boolean value of
+ * the override for the app in this version range. Ranges can be open ended in either direction.
+ * An instance of PackageOverride gets created via {@link Builder} and is immutable once created.
+ *
+ * @hide
+ */
+public class PackageOverride implements Parcelable {
+
+    @IntDef({
+            VALUE_UNDEFINED,
+            VALUE_ENABLED,
+            VALUE_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
+    public @interface EvaluatedOverride {
+    }
+
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * this PackageOverride does not define the value of the override for the given version.
+     * @hide
+     */
+    public static final int VALUE_UNDEFINED = 0;
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * the override evaluates to {@code true} for the given version.
+     * @hide
+     */
+    public static final int VALUE_ENABLED = 1;
+    /**
+     * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that
+     * the override evaluates to {@code fakse} for the given version.
+     * @hide
+     */
+    public static final int VALUE_DISABLED = 2;
+
+    private final long mMinVersionCode;
+    private final long mMaxVersionCode;
+    private final boolean mEnabled;
+
+    private PackageOverride(long minVersionCode,
+            long maxVersionCode,
+            boolean enabled) {
+        this.mMinVersionCode = minVersionCode;
+        this.mMaxVersionCode = maxVersionCode;
+        this.mEnabled = enabled;
+    }
+
+    private PackageOverride(Parcel in) {
+        this(in.readLong(), in.readLong(), in.readBoolean());
+    }
+
+    /**
+     * Evaluate the override for the given {@code versionCode}. If no override is defined for
+     * the specified version code, {@link #VALUE_UNDEFINED} is returned.
+     * @hide
+     */
+    public @EvaluatedOverride int evaluate(long versionCode) {
+        if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) {
+            return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+        }
+        return VALUE_UNDEFINED;
+    }
+
+    /**
+     * Evaluate the override independent of version code, i.e. only return an evaluated value if
+     * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned.
+     * @hide
+     */
+    public int evaluateForAllVersions() {
+        if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+            return mEnabled ? VALUE_ENABLED : VALUE_DISABLED;
+        }
+        return VALUE_UNDEFINED;
+    }
+
+    /** Returns the minimum version code the override applies to. */
+    public long getMinVersionCode() {
+        return mMinVersionCode;
+    }
+
+    /** Returns the minimum version code the override applies from. */
+    public long getMaxVersionCode() {
+        return mMaxVersionCode;
+    }
+
+    /** Returns the enabled value for the override. */
+    public boolean getEnabled() {
+        return mEnabled;
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mMinVersionCode);
+        dest.writeLong(mMaxVersionCode);
+        dest.writeBoolean(mEnabled);
+    }
+
+    /** @hide */
+    @Override
+    public String toString() {
+        if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
+            return Boolean.toString(mEnabled);
+        }
+        return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled);
+    }
+
+    /** @hide */
+    public static final Creator<PackageOverride> CREATOR =
+            new Creator<PackageOverride>() {
+
+                @Override
+                public PackageOverride createFromParcel(Parcel in) {
+                    return new PackageOverride(in);
+                }
+
+                @Override
+                public PackageOverride[] newArray(int size) {
+                    return new PackageOverride[size];
+                }
+            };
+
+    /**
+     * Builder to construct a PackageOverride.
+     */
+    public static class Builder {
+        private long mMinVersionCode = Long.MIN_VALUE;
+        private long mMaxVersionCode = Long.MAX_VALUE;
+        private boolean mEnabled;
+
+        /**
+         * Sets the minimum version code the override should apply from.
+         *
+         * default value: {@code Long.MIN_VALUE}.
+         */
+        public Builder setMinVersionCode(long minVersionCode) {
+            mMinVersionCode = minVersionCode;
+            return this;
+        }
+
+        /**
+         * Sets the maximum version code the override should apply to.
+         *
+         * default value: {@code Long.MAX_VALUE}.
+         */
+        public Builder setMaxVersionCode(long maxVersionCode) {
+            mMaxVersionCode = maxVersionCode;
+            return this;
+        }
+
+        /**
+         * Sets whether the override should be enabled for the given version range.
+         *
+         * default value: {@code false}.
+         */
+        public Builder setEnabled(boolean enabled) {
+            mEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Build the {@link PackageOverride}.
+         *
+         * @throws IllegalArgumentException if {@code minVersionCode} is larger than
+         *                                  {@code maxVersionCode}.
+         */
+        public PackageOverride build() {
+            if (mMinVersionCode > mMaxVersionCode) {
+                throw new IllegalArgumentException("minVersionCode must not be larger than "
+                        + "maxVersionCode");
+            }
+            return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled);
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index d451599..e6fdc00 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -60,7 +60,7 @@
     public void postExecute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         // TODO(lifecycler): Use interface callback instead of actual implementation.
-        ActivityClient.getInstance().activityResumed(token);
+        ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
     }
 
     @Override
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
new file mode 100644
index 0000000..5374984
--- /dev/null
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -0,0 +1,105 @@
+/*
+ * 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.app.servertransaction;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Transfer a splash screen view to an Activity.
+ * @hide
+ */
+public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
+
+    private SplashScreenViewParcelable mSplashScreenViewParcelable;
+    private @TransferRequest int mRequest;
+
+    @IntDef(value = {
+            ATTACH_TO,
+            HANDOVER_TO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransferRequest {}
+    // request client to attach the view on it.
+    public static final int ATTACH_TO = 0;
+    // tell client that you can handle the splash screen view.
+    public static final int HANDOVER_TO = 1;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull ActivityThread.ActivityClientRecord r,
+            PendingTransactionActions pendingActions) {
+        switch (mRequest) {
+            case ATTACH_TO:
+                client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
+                break;
+            case HANDOVER_TO:
+                client.handOverSplashScreenView(r);
+                break;
+        }
+    }
+
+    @Override
+    public void recycle() {
+        ObjectPool.recycle(this);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequest);
+        dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+    }
+
+    private TransferSplashScreenViewStateItem() {}
+    private TransferSplashScreenViewStateItem(Parcel in) {
+        mRequest = in.readInt();
+        mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+    }
+
+    /** Obtain an instance initialized with provided params. */
+    public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
+            @Nullable SplashScreenViewParcelable parcelable) {
+        TransferSplashScreenViewStateItem instance =
+                ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
+        if (instance == null) {
+            instance = new TransferSplashScreenViewStateItem();
+        }
+        instance.mRequest = state;
+        instance.mSplashScreenViewParcelable = parcelable;
+
+        return instance;
+    }
+
+    public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
+            new Creator<TransferSplashScreenViewStateItem>() {
+                public TransferSplashScreenViewStateItem createFromParcel(Parcel in) {
+                    return new TransferSplashScreenViewStateItem(in);
+                }
+
+                public TransferSplashScreenViewStateItem[] newArray(int size) {
+                    return new TransferSplashScreenViewStateItem[size];
+                }
+            };
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 1e0653d..920b9fe 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -138,6 +138,15 @@
         dest.writeInt(mEventType);
     }
 
+    @Override
+    public String toString() {
+        return "SmartspaceTargetEvent{"
+                + "mSmartspaceTarget=" + mSmartspaceTarget
+                + ", mSmartspaceActionId='" + mSmartspaceActionId + '\''
+                + ", mEventType=" + mEventType
+                + '}';
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/app/time/ExternalTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
rename to core/java/android/app/time/ExternalTimeSuggestion.aidl
index 14d57bf..07a0fbb 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/app/time/ExternalTimeSuggestion.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 parcelable ExternalTimeSuggestion;
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
similarity index 99%
rename from core/java/android/app/timedetector/ExternalTimeSuggestion.java
rename to core/java/android/app/time/ExternalTimeSuggestion.java
index 7ad303a..b566eab 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 4626543..c4546be 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,7 +16,7 @@
 
 package android.app.timedetector;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index df8d797..76f3785 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.TimestampedValue;
diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java
index f80869f..ef818ef 100644
--- a/core/java/android/app/timedetector/TimeDetectorImpl.java
+++ b/core/java/android/app/timedetector/TimeDetectorImpl.java
@@ -17,6 +17,7 @@
 package android.app.timedetector;
 
 import android.annotation.NonNull;
+import android.app.time.ExternalTimeSuggestion;
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index a3c3a0e..42d90a7 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -16,6 +16,7 @@
 
 package android.appwidget;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -29,6 +30,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
@@ -53,6 +55,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 /**
@@ -89,6 +92,7 @@
     int mLayoutId = -1;
     private OnClickHandler mOnClickHandler;
     private boolean mOnLightBackground;
+    PointF mCurrentSize = null;
 
     private Executor mAsyncExecutor;
     private CancellationSignal mLastExecutionSignal;
@@ -268,7 +272,8 @@
      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
      * the framework will be accounted for automatically. This information gets embedded into the
-     * AppWidget options and causes a callback to the AppWidgetProvider.
+     * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
+     * sizes is explicitly set to an empty list.
      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
      *
      * @param newOptions The bundle of options, in addition to the size information,
@@ -277,14 +282,97 @@
      * @param minHeight The maximum height in dips that the widget will be displayed at.
      * @param maxWidth The maximum width in dips that the widget will be displayed at.
      * @param maxHeight The maximum height in dips that the widget will be displayed at.
-     *
+     * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
      */
+    @Deprecated
     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
             int maxHeight) {
         updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
     }
 
     /**
+     * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should
+     * correspond to the full area the AppWidgetHostView is given. Padding added by the framework
+     * will be accounted for automatically.
+     *
+     * This method will update the option bundle with the list of sizes and the min/max bounds for
+     * width and height.
+     *
+     * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
+     *
+     * @param newOptions The bundle of options, in addition to the size information.
+     * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
+     *              again. Typically, this will be size of the widget in landscape and portrait.
+     *              On some foldables, this might include the size on the outer and inner screens.
+     */
+    public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<PointF> sizes) {
+        AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
+
+        Rect padding = getDefaultPadding();
+        float density = getResources().getDisplayMetrics().density;
+
+        float xPaddingDips = (padding.left + padding.right) / density;
+        float yPaddingDips = (padding.top + padding.bottom) / density;
+
+        ArrayList<PointF> paddedSizes = new ArrayList<>(sizes.size());
+        float minWidth = Float.MAX_VALUE;
+        float maxWidth = 0;
+        float minHeight = Float.MAX_VALUE;
+        float maxHeight = 0;
+        for (int i = 0; i < sizes.size(); i++) {
+            PointF size = sizes.get(i);
+            PointF paddedPoint = new PointF(Math.max(0.f, size.x - xPaddingDips),
+                    Math.max(0.f, size.y - yPaddingDips));
+            paddedSizes.add(paddedPoint);
+            minWidth = Math.min(minWidth, paddedPoint.x);
+            maxWidth = Math.max(maxWidth, paddedPoint.x);
+            minHeight = Math.min(minHeight, paddedPoint.y);
+            maxHeight = Math.max(maxHeight, paddedPoint.y);
+        }
+        if (paddedSizes.equals(
+                widgetManager.getAppWidgetOptions(mAppWidgetId).<PointF>getParcelableArrayList(
+                        AppWidgetManager.OPTION_APPWIDGET_SIZES))) {
+            return;
+        }
+        Bundle options = newOptions.deepCopy();
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth);
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight);
+        options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
+        updateAppWidgetOptions(options);
+    }
+
+    /**
+     * Set the current size of the widget. This should be the full area the AppWidgetHostView is
+     * given. Padding added by the framework will be accounted for automatically.
+     *
+     * This size will be used to choose the appropriate layout the next time the {@link RemoteViews}
+     * is re-inflated, if it was created with {@link RemoteViews#RemoteViews(Map)} .
+     */
+    public void setCurrentSize(@NonNull PointF size) {
+        Rect padding = getDefaultPadding();
+        float density = getResources().getDisplayMetrics().density;
+        float xPaddingDips = (padding.left + padding.right) / density;
+        float yPaddingDips = (padding.top + padding.bottom) / density;
+        PointF newSize = new PointF(size.x - xPaddingDips, size.y - yPaddingDips);
+        if (!newSize.equals(mCurrentSize)) {
+            mCurrentSize = newSize;
+            mLayoutId = -1; // Prevents recycling the view.
+        }
+    }
+
+    /**
+     * Clear the current size, indicating it is not currently known.
+     */
+    public void clearCurrentSize() {
+        if (mCurrentSize != null) {
+            mCurrentSize = null;
+            mLayoutId = -1;
+        }
+    }
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -322,6 +410,8 @@
             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
+            newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
+                    new ArrayList<PointF>());
             updateAppWidgetOptions(newOptions);
         }
     }
@@ -440,7 +530,7 @@
             // Try normal RemoteView inflation
             if (content == null) {
                 try {
-                    content = remoteViews.apply(mContext, this, mOnClickHandler);
+                    content = remoteViews.apply(mContext, this, mOnClickHandler, mCurrentSize);
                     if (LOGD) Log.d(TAG, "had to inflate new layout");
                 } catch (RuntimeException e) {
                     exception = e;
@@ -492,7 +582,8 @@
                         mView,
                         mAsyncExecutor,
                         new ViewApplyListener(remoteViews, layoutId, true),
-                        mOnClickHandler);
+                        mOnClickHandler,
+                        mCurrentSize);
             } catch (Exception e) {
                 // Reapply failed. Try apply
             }
@@ -502,7 +593,8 @@
                     this,
                     mAsyncExecutor,
                     new ViewApplyListener(remoteViews, layoutId, false),
-                    mOnClickHandler);
+                    mOnClickHandler,
+                    mCurrentSize);
         }
     }
 
@@ -533,7 +625,8 @@
                         AppWidgetHostView.this,
                         mAsyncExecutor,
                         new ViewApplyListener(mViews, mLayoutId, false),
-                        mOnClickHandler);
+                        mOnClickHandler,
+                        mCurrentSize);
             } else {
                 applyContent(null, false, e);
             }
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 37093a1..aac8710 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -217,6 +217,12 @@
     public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight";
 
     /**
+     * A bundle extra ({@code List<PointF>}) that contains the list of possible sizes, in dips, a
+     * widget instance can take.
+     */
+    public static final String OPTION_APPWIDGET_SIZES = "appWidgetSizes";
+
+    /**
      * A bundle extra that hints to the AppWidgetProvider the category of host that owns this
      * this widget. Can have the value {@link
      * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ea7e5ea..ec46da0 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1654,7 +1654,7 @@
         mContext = context;
     }
 
-    private String getOpPackageName() {
+    String getOpPackageName() {
         // Workaround for legacy API for getting a BluetoothAdapter not
         // passing a context
         if (mContext != null) {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 3b8dec7..e7661db 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1236,7 +1236,8 @@
             return false;
         }
         try {
-            return service.createBond(this, transport, oobData);
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            return service.createBond(this, transport, oobData, adapter.getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1394,6 +1395,31 @@
     }
 
     /**
+     * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip
+     * the bluetooth pairing dialog because it has been already consented by the CDM prompt.
+     *
+     * @return true if we can bond without the dialog, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean canBondWithoutDialog() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog");
+            return false;
+        }
+        try {
+            if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this);
+            return service.canBondWithoutDialog(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
      * Returns whether there is an open connection to this device.
      *
      * @return True if there is at least one open connection to this device.
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 0188637..f3ecbf6 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -740,6 +740,7 @@
         mIcon = null;
         mItems = new ArrayList<Item>();
         mItems.add(item);
+        mClipDescription.setIsStyledText(isStyledText());
     }
 
     /**
@@ -756,6 +757,7 @@
         mIcon = null;
         mItems = new ArrayList<Item>();
         mItems.add(item);
+        mClipDescription.setIsStyledText(isStyledText());
     }
 
     /**
@@ -914,6 +916,9 @@
             throw new NullPointerException("item is null");
         }
         mItems.add(item);
+        if (mItems.size() == 1) {
+            mClipDescription.setIsStyledText(isStyledText());
+        }
     }
 
     /**
@@ -1049,6 +1054,20 @@
         }
     }
 
+    private boolean isStyledText() {
+        if (mItems.isEmpty()) {
+            return false;
+        }
+        final CharSequence text = mItems.get(0).getText();
+        if (text instanceof Spanned) {
+            Spanned spanned = (Spanned) text;
+            if (TextUtils.hasStyleSpan(spanned)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index e3395e2..d48f832 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -120,6 +120,7 @@
     private final ArrayList<String> mMimeTypes;
     private PersistableBundle mExtras;
     private long mTimeStamp;
+    private boolean mIsStyledText;
 
     /**
      * Create a new clip.
@@ -325,6 +326,26 @@
         }
     }
 
+    /**
+     * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e.
+     * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link
+     * android.text.style.ParagraphStyle ParagraphStyle}, or {@link
+     * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if
+     * there is no associated clip data.
+     */
+    public boolean isStyledText() {
+        return mIsStyledText;
+    }
+
+    /**
+     * Sets whether the associated {@link ClipData} contains styled text in its first item. This
+     * should be called when this description is associated with clip data or when the first item
+     * is added to the associated clip data.
+     */
+    void setIsStyledText(boolean isStyledText) {
+        mIsStyledText = isStyledText;
+    }
+
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
@@ -429,6 +450,7 @@
         dest.writeStringList(mMimeTypes);
         dest.writePersistableBundle(mExtras);
         dest.writeLong(mTimeStamp);
+        dest.writeBoolean(mIsStyledText);
     }
 
     ClipDescription(Parcel in) {
@@ -436,6 +458,7 @@
         mMimeTypes = in.createStringArrayList();
         mExtras = in.readPersistableBundle();
         mTimeStamp = in.readLong();
+        mIsStyledText = in.readBoolean();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index d7dc86a..46d8900 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -63,7 +63,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.system.Int32Ref;
+import android.system.Int64Ref;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -4050,7 +4050,7 @@
         // Convert to Point, since that's what the API is defined as
         final Bundle opts = new Bundle();
         opts.putParcelable(EXTRA_SIZE, new Point(size.getWidth(), size.getHeight()));
-        final Int32Ref orientation = new Int32Ref(0);
+        final Int64Ref orientation = new Int64Ref(0);
 
         Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> {
             final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a402b2..24b7629 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -93,6 +93,7 @@
 import java.io.InputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -3479,6 +3480,7 @@
             STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
             TIME_ZONE_RULES_MANAGER_SERVICE,
+            VIBRATOR_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
@@ -3625,9 +3627,11 @@
      *   (e.g., GPS) updates.
      *  <dt> {@link #SEARCH_SERVICE} ("search")
      *  <dd> A {@link android.app.SearchManager} for handling search.
+     *  <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager")
+     *  <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting
+     *  with individual ones and playing synchronized effects on multiple vibrators.
      *  <dt> {@link #VIBRATOR_SERVICE} ("vibrator")
-     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator
-     *  hardware.
+     *  <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware.
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
@@ -3707,6 +3711,8 @@
      * @see android.hardware.SensorManager
      * @see #STORAGE_SERVICE
      * @see android.os.storage.StorageManager
+     * @see #VIBRATOR_MANAGER_SERVICE
+     * @see android.os.VibratorManager
      * @see #VIBRATOR_SERVICE
      * @see android.os.Vibrator
      * @see #CONNECTIVITY_SERVICE
@@ -4034,8 +4040,19 @@
     public static final String WALLPAPER_SERVICE = "wallpaper";
 
     /**
-     * Use with {@link #getSystemService(String)} to retrieve a {@link
-     * android.os.Vibrator} for interacting with the vibration hardware.
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager}
+     * for accessing the device vibrators, interacting with individual ones and playing synchronized
+     * effects on multiple vibrators.
+     *
+     * @see #getSystemService(String)
+     * @see android.os.VibratorManager
+     */
+    @SuppressLint("ServiceName")
+    public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for
+     * interacting with the vibration hardware.
      *
      * @see #getSystemService(String)
      * @see android.os.Vibrator
@@ -5709,6 +5726,32 @@
     public abstract int checkUriPermission(Uri uri, int pid, int uid,
             @Intent.AccessUriMode int modeFlags);
 
+    /**
+     * Determine whether a particular process and user ID has been granted
+     * permission to access a list of URIs.  This only checks for permissions
+     * that have been explicitly granted -- if the given process/uid has
+     * more general access to the URI's content provider then this check will
+     * always fail.
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param pid The process ID being checked against.  Must be &gt; 0.
+     * @param uid The user ID being checked against.  A uid of 0 is the root
+     * user, which will pass every permission check.
+     * @param modeFlags The access modes to check for the list of uris
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
     /** @hide */
     @SuppressWarnings("HiddenAbstractMethod")
     @PackageManager.PermissionResult
@@ -5739,6 +5782,32 @@
     public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
 
     /**
+     * Determine whether the calling process and user ID has been
+     * granted permission to access a list of URIs.  This is basically
+     * the same as calling {@link #checkUriPermissions(List, int, int, int)}
+     * with the pid and uid returned by {@link
+     * android.os.Binder#getCallingPid} and {@link
+     * android.os.Binder#getCallingUid}.  One important difference is
+     * that if you are not currently processing an IPC, this function
+     * will always fail.
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param modeFlags The access modes to check.
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkUriPermission(Uri, int, int, int)
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Determine whether the calling process of an IPC <em>or you</em> has been granted
      * permission to access a specific URI.  This is the same as
      * {@link #checkCallingUriPermission}, except it grants your own permissions
@@ -5759,6 +5828,28 @@
             @Intent.AccessUriMode int modeFlags);
 
     /**
+     * Determine whether the calling process of an IPC <em>or you</em> has been granted
+     * permission to access a list of URIs.  This is the same as
+     * {@link #checkCallingUriPermission}, except it grants your own permissions
+     * if you are not currently processing an IPC.  Use with care!
+     *
+     * @param uris The list of URIs that is being checked.
+     * @param modeFlags The access modes to check.
+     *
+     * @return Array of permission grants corresponding to each entry in the list of uris.
+     * {@link PackageManager#PERMISSION_GRANTED} if the given pid/uid is allowed to access that uri,
+     * or {@link PackageManager#PERMISSION_DENIED} if it is not.
+     *
+     * @see #checkCallingUriPermission
+     */
+    @NonNull
+    @PackageManager.PermissionResult
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris,
+            @Intent.AccessUriMode int modeFlags) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Check both a Uri and normal permission.  This allows you to perform
      * both {@link #checkPermission} and {@link #checkUriPermission} in one
      * call.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c1c213e..b71fb27 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -53,6 +53,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -905,6 +906,13 @@
         return mBase.checkUriPermission(uri, pid, uid, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            int modeFlags) {
+        return mBase.checkUriPermissions(uris, pid, uid, modeFlags);
+    }
+
     /** @hide */
     @Override
     public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) {
@@ -916,11 +924,23 @@
         return mBase.checkCallingUriPermission(uri, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return mBase.checkCallingUriPermissions(uris, modeFlags);
+    }
+
     @Override
     public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
         return mBase.checkCallingOrSelfUriPermission(uri, modeFlags);
     }
 
+    @NonNull
+    @Override
+    public int[] checkCallingOrSelfUriPermissions(@NonNull List<Uri> uris, int modeFlags) {
+        return mBase.checkCallingOrSelfUriPermissions(uris, modeFlags);
+    }
+
     @Override
     public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
             @Nullable String writePermission, int pid, int uid, int modeFlags) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30b2404..4abd8cd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5718,7 +5718,7 @@
     public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
 
     /**
-     * Used in the extra field in the remote intent. It's astring token passed with the
+     * Used in the extra field in the remote intent. It's a string token passed with the
      * remote intent.
      */
     public static final String EXTRA_REMOTE_INTENT_TOKEN =
@@ -6062,6 +6062,16 @@
      */
     public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
 
+    /**
+     * A boolean extra indicating whether an activity is bubbled. Set on the shortcut or
+     * pending intent provided for the bubble. If the extra is not present or false, then it is not
+     * bubbled.
+     *
+     * @see android.app.Notification.Builder#setBubbleMetadata(Notification.BubbleMetadata)
+     * @see android.app.Notification.BubbleMetadata.Builder#Builder(String)
+     */
+    public static final String EXTRA_IS_BUBBLED = "android.intent.extra.IS_BUBBLED";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/om/CriticalOverlayInfo.java b/core/java/android/content/om/CriticalOverlayInfo.java
new file mode 100644
index 0000000..8fbc698
--- /dev/null
+++ b/core/java/android/content/om/CriticalOverlayInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * A subset of {@link OverlayInfo} fields that when changed cause the overlay's settings to be
+ * completely reinitialized.
+ *
+ * @hide
+ */
+public interface CriticalOverlayInfo {
+
+    /**
+     * @return the package name of the overlay.
+     */
+    @NonNull
+    String getPackageName();
+
+    /**
+     * @return the unique name of the overlay within its containing package.
+     */
+    @Nullable
+    String getOverlayName();
+
+    /**
+     * @return the target package name of the overlay.
+     */
+    @NonNull
+    String getTargetPackageName();
+
+    /**
+     * @return the name of the target overlayable declaration.
+     */
+    @Nullable
+    String getTargetOverlayableName();
+
+    /**
+     * @return an identifier representing the current overlay.
+     */
+    @NonNull
+    OverlayIdentifier getOverlayIdentifier();
+
+    /**
+     * Returns whether or not the overlay is a {@link FabricatedOverlay}.
+     */
+    boolean isFabricated();
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
new file mode 100644
index 0000000..d62b47b
--- /dev/null
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -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 android.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInternal;
+import android.os.FabricatedOverlayInternalEntry;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ *
+ * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+ * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+ * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
+ * system partition would fulfil the {@code system} and {@code signature} policies.
+ *
+ * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+ * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ *
+ * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
+ * overlays and fabricated overlays.
+ * @hide
+ */
+public class FabricatedOverlay {
+
+    /** Retrieves the identifier for this fabricated overlay. */
+    public OverlayIdentifier getIdentifier() {
+        return new OverlayIdentifier(
+                mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
+    }
+
+    public static class Builder {
+        private final String mOwningPackage;
+        private final String mName;
+        private final String mTargetPackage;
+        private String mTargetOverlayable = "";
+        private final ArrayList<FabricatedOverlayInternalEntry> mEntries = new ArrayList<>();
+
+        /**
+         * Constructs a build for a fabricated overlay.
+         *
+         * @param owningPackage the name of the package that owns the fabricated overlay (must
+         *                      be a package name of this UID).
+         * @param name a name used to uniquely identify the fabricated overlay owned by
+         *             {@param owningPackageName}
+         * @param targetPackage the name of the package to overlay
+         */
+        public Builder(@NonNull String owningPackage, @NonNull String name,
+                @NonNull String targetPackage) {
+            Preconditions.checkStringNotEmpty(owningPackage,
+                    "'owningPackage' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(name,
+                    "'name'' must not be empty nor null");
+            Preconditions.checkStringNotEmpty(targetPackage,
+                    "'targetPackage' must not be empty nor null");
+
+            mOwningPackage = owningPackage;
+            mName = name;
+            mTargetPackage = targetPackage;
+        }
+
+        /**
+         * Sets the name of the overlayable resources to overlay (can be null).
+         */
+        public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
+            mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+            return this;
+        }
+
+        /**
+         * Sets the value of
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *                     [package]:type/entry)
+         * @param dataType the data type of the new value
+         * @param value the unsigned 32 bit integer representing the new value
+         *
+         * @see android.util.TypedValue#type
+         */
+        public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+            entry.resourceName = resourceName;
+            entry.dataType = dataType;
+            entry.data = value;
+            mEntries.add(entry);
+            return this;
+        }
+
+        /** Builds an immutable fabricated overlay. */
+        public FabricatedOverlay build() {
+            final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+            overlay.packageName = mOwningPackage;
+            overlay.overlayName = mName;
+            overlay.targetPackageName = mTargetPackage;
+            overlay.targetOverlayable = mTargetOverlayable;
+            overlay.entries = new ArrayList<>();
+            overlay.entries.addAll(mEntries);
+            return new FabricatedOverlay(overlay);
+        }
+    }
+
+    final FabricatedOverlayInternal mOverlay;
+    private FabricatedOverlay(FabricatedOverlayInternal overlay) {
+        mOverlay = overlay;
+    }
+}
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 0b950b4..e319d2c 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -16,6 +16,7 @@
 
 package android.content.om;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManagerTransaction;
 
@@ -66,6 +67,17 @@
     OverlayInfo getOverlayInfo(in String packageName, in int userId);
 
     /**
+     * Returns information about the overlay with the given package name for the
+     * specified user.
+     *
+     * @param packageName The name of the overlay package.
+     * @param userId The user to get the OverlayInfo for.
+     * @return The OverlayInfo for the overlay package; or null if no such
+     *         overlay package exists.
+     */
+    OverlayInfo getOverlayInfoByIdentifier(in OverlayIdentifier packageName, in int userId);
+
+    /**
      * Request that an overlay package be enabled or disabled when possible to
      * do so.
      *
@@ -163,7 +175,7 @@
      * Invalidates and removes the idmap for an overlay,
      * @param packageName The name of the overlay package whose idmap should be deleted.
      */
-    void invalidateCachesForOverlay(in String packageName, in int userIs);
+    void invalidateCachesForOverlay(in String packageName, in int userId);
 
     /**
      * Perform a series of requests related to overlay packages. This is an
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/content/om/OverlayIdentifier.aidl
similarity index 89%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/content/om/OverlayIdentifier.aidl
index 14d57bf..d1c7770 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/content/om/OverlayIdentifier.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.content.om;
 
-parcelable ExternalTimeSuggestion;
+parcelable OverlayIdentifier;
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
new file mode 100644
index 0000000..454d0d1
--- /dev/null
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -0,0 +1,208 @@
+/*
+ * 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.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ *
+ * An overlay always belongs to a package and may optionally have a name associated with it.
+ * The name helps uniquely identify a particular overlay within a package.
+ * @hide
+ */
+/** @hide */
+@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
+        genEqualsHashCode = true, genToString = false)
+public class OverlayIdentifier implements Parcelable  {
+    /**
+     * The package name containing or owning the overlay.
+     */
+    @Nullable
+    private final String mPackageName;
+
+    /**
+     * The unique name within the package of the overlay.
+     */
+    @Nullable
+    private final String mOverlayName;
+
+    /**
+     * Creates an identifier from a package and unique name within the package.
+     *
+     * @param packageName the package containing or owning the overlay
+     * @param overlayName the unique name of the overlay within the package
+     */
+    public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
+        mPackageName = packageName;
+        mOverlayName = overlayName;
+    }
+
+    /**
+     * Creates an identifier for an overlay without a name.
+     *
+     * @param packageName the package containing or owning the overlay
+     */
+    public OverlayIdentifier(@NonNull String packageName) {
+        mPackageName = packageName;
+        mOverlayName = null;
+    }
+
+    @Override
+    public String toString() {
+        return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
+    }
+
+    /** @hide */
+    public static OverlayIdentifier fromString(@NonNull String text) {
+        final String[] parts = text.split(":", 2);
+        if (parts.length == 2) {
+            return new OverlayIdentifier(parts[0], parts[1]);
+        } else {
+            return new OverlayIdentifier(parts[0]);
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayIdentifier.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Retrieves the package name containing or owning the overlay.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Retrieves the unique name within the package of the overlay.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getOverlayName() {
+        return mOverlayName;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(OverlayIdentifier other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        OverlayIdentifier that = (OverlayIdentifier) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mOverlayName, that.mOverlayName);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + Objects.hashCode(mOverlayName);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mPackageName != null) flg |= 0x1;
+        if (mOverlayName != null) flg |= 0x2;
+        dest.writeByte(flg);
+        if (mPackageName != null) dest.writeString(mPackageName);
+        if (mOverlayName != null) dest.writeString(mOverlayName);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected OverlayIdentifier(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String packageName = (flg & 0x1) == 0 ? null : in.readString();
+        String overlayName = (flg & 0x2) == 0 ? null : in.readString();
+
+        this.mPackageName = packageName;
+        this.mOverlayName = overlayName;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<OverlayIdentifier> CREATOR
+            = new Parcelable.Creator<OverlayIdentifier>() {
+        @Override
+        public OverlayIdentifier[] newArray(int size) {
+            return new OverlayIdentifier[size];
+        }
+
+        @Override
+        public OverlayIdentifier createFromParcel(@NonNull Parcel in) {
+            return new OverlayIdentifier(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1612482438728L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static  android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 517e4bd..c66f49c 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -26,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -37,7 +39,7 @@
  * @hide
  */
 @SystemApi
-public final class OverlayInfo implements Parcelable {
+public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
 
     /** @hide */
     @IntDef(prefix = "STATE_", value = {
@@ -143,6 +145,14 @@
     public final String packageName;
 
     /**
+     * The unique name within the package of the overlay.
+     *
+     * @hide
+     */
+    @Nullable
+    public final String overlayName;
+
+    /**
      * Package name of the target package
      *
      * @hide
@@ -201,6 +211,14 @@
      */
     public final boolean isMutable;
 
+    private OverlayIdentifier mIdentifierCached;
+
+    /**
+     *
+     * @hide
+     */
+    public final boolean isFabricated;
+
     /**
      * Create a new OverlayInfo based on source with an updated state.
      *
@@ -210,17 +228,28 @@
      * @hide
      */
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
-        this(source.packageName, source.targetPackageName, source.targetOverlayableName,
-                source.category, source.baseCodePath, state, source.userId, source.priority,
-                source.isMutable);
+        this(source.packageName, source.overlayName, source.targetPackageName,
+                source.targetOverlayableName, source.category, source.baseCodePath, state,
+                source.userId, source.priority, source.isMutable, source.isFabricated);
     }
 
     /** @hide */
+    @VisibleForTesting
     public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
             @Nullable String targetOverlayableName, @Nullable String category,
-            @NonNull String baseCodePath, int state, int userId,
-            int priority, boolean isMutable) {
+            @NonNull String baseCodePath, int state, int userId, int priority, boolean isMutable) {
+        this(packageName, null /* overlayName */, targetPackageName, targetOverlayableName,
+                category, baseCodePath, state, userId, priority, isMutable,
+                false /* isFabricated */);
+    }
+
+    /** @hide */
+    public OverlayInfo(@NonNull String packageName, @Nullable String overlayName,
+            @NonNull String targetPackageName, @Nullable String targetOverlayableName,
+            @Nullable String category, @NonNull String baseCodePath, int state, int userId,
+            int priority, boolean isMutable, boolean isFabricated) {
         this.packageName = packageName;
+        this.overlayName = overlayName;
         this.targetPackageName = targetPackageName;
         this.targetOverlayableName = targetOverlayableName;
         this.category = category;
@@ -229,12 +258,14 @@
         this.userId = userId;
         this.priority = priority;
         this.isMutable = isMutable;
+        this.isFabricated = isFabricated;
         ensureValidState();
     }
 
     /** @hide */
     public OverlayInfo(Parcel source) {
         packageName = source.readString();
+        overlayName = source.readString();
         targetPackageName = source.readString();
         targetOverlayableName = source.readString();
         category = source.readString();
@@ -243,13 +274,15 @@
         userId = source.readInt();
         priority = source.readInt();
         isMutable = source.readBoolean();
+        isFabricated = source.readBoolean();
         ensureValidState();
     }
 
     /**
-     * Returns package name of the current overlay.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
     @SystemApi
     @NonNull
     public String getPackageName() {
@@ -257,9 +290,20 @@
     }
 
     /**
-     * Returns the target package name of the current overlay.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
+    @Nullable
+    public String getOverlayName() {
+        return overlayName;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
     @SystemApi
     @NonNull
     public String getTargetPackageName() {
@@ -268,7 +312,8 @@
 
     /**
      * Returns the category of the current overlay.
-     * @hide\
+     *
+     * @hide
      */
     @SystemApi
     @Nullable
@@ -278,6 +323,7 @@
 
     /**
      * Returns user handle for which this overlay applies to.
+     *
      * @hide
      */
     @SystemApi
@@ -287,15 +333,47 @@
     }
 
     /**
-     * Returns name of the target overlayable declaration.
+     * {@inheritDoc}
      * @hide
      */
+    @Override
     @SystemApi
     @Nullable
     public String getTargetOverlayableName() {
         return targetOverlayableName;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
+    public boolean isFabricated() {
+        return isFabricated;
+    }
+
+    /**
+     * Full path to the base APK or fabricated overlay for this overlay package.
+     *
+     * @hide
+     */
+    public String getBaseCodePath() {
+        return baseCodePath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
+    @NonNull
+    public OverlayIdentifier getOverlayIdentifier() {
+        if (mIdentifierCached == null) {
+            mIdentifierCached = new OverlayIdentifier(packageName, overlayName);
+        }
+        return mIdentifierCached;
+    }
+
     @SuppressWarnings("ConstantConditions")
     private void ensureValidState() {
         if (packageName == null) {
@@ -330,6 +408,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(packageName);
+        dest.writeString(overlayName);
         dest.writeString(targetPackageName);
         dest.writeString(targetOverlayableName);
         dest.writeString(category);
@@ -338,6 +417,7 @@
         dest.writeInt(userId);
         dest.writeInt(priority);
         dest.writeBoolean(isMutable);
+        dest.writeBoolean(isFabricated);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
@@ -410,6 +490,7 @@
         result = prime * result + userId;
         result = prime * result + state;
         result = prime * result + ((packageName == null) ? 0 : packageName.hashCode());
+        result = prime * result + ((overlayName == null) ? 0 : overlayName.hashCode());
         result = prime * result + ((targetPackageName == null) ? 0 : targetPackageName.hashCode());
         result = prime * result + ((targetOverlayableName == null) ? 0
                 : targetOverlayableName.hashCode());
@@ -439,6 +520,9 @@
         if (!packageName.equals(other.packageName)) {
             return false;
         }
+        if (!Objects.equals(overlayName, other.overlayName)) {
+            return false;
+        }
         if (!targetPackageName.equals(other.targetPackageName)) {
             return false;
         }
@@ -457,9 +541,13 @@
     @NonNull
     @Override
     public String toString() {
-        return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
-                + ((targetOverlayableName == null) ? ""
-                : ", targetOverlayable=" + targetOverlayableName)
-                + ", state=" + state + " (" + stateToString(state) + "), userId=" + userId + " }";
+        return "OverlayInfo {"
+                + "packageName=" + packageName
+                + ", overlayName=" + overlayName
+                + ", targetPackage=" + targetPackageName
+                + ", targetOverlayable=" + targetOverlayableName
+                + ", state=" + state + " (" + stateToString(state) + "),"
+                + ", userId=" + userId
+                + " }";
     }
 }
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7c14c28..0f7e01b 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -205,6 +205,25 @@
     }
 
     /**
+     * Returns information about the overlay represented by the identifier for the specified user.
+     *
+     * @param overlay the identifier representing the overlay
+     * @param userHandle the user of which to get overlay state info
+     * @return the overlay info or null if the overlay cannot be found
+     *
+     * @hide
+     */
+    @Nullable
+    public OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay,
+            @NonNull final UserHandle userHandle) {
+        try {
+            return mService.getOverlayInfoByIdentifier(overlay, userHandle.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns information about all overlays for the given target package for
      * the specified user. The returned list is ordered according to the
      * overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 1fa8973..73be0ff 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,6 +20,9 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -29,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Container for a batch of requests to the OverlayManagerService.
@@ -60,12 +64,13 @@
 
     private OverlayManagerTransaction(@NonNull final Parcel source) {
         final int size = source.readInt();
-        mRequests = new ArrayList<Request>(size);
+        mRequests = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final int request = source.readInt();
-            final String packageName = source.readString();
+            final OverlayIdentifier overlay = source.readParcelable(null);
             final int userId = source.readInt();
-            mRequests.add(new Request(request, packageName, userId));
+            final Bundle extras = source.readBundle(null);
+            mRequests.add(new Request(request, overlay, userId, extras));
         }
     }
 
@@ -95,22 +100,36 @@
 
         public static final int TYPE_SET_ENABLED = 0;
         public static final int TYPE_SET_DISABLED = 1;
+        public static final int TYPE_REGISTER_FABRICATED = 2;
+        public static final int TYPE_UNREGISTER_FABRICATED = 3;
 
-        @RequestType public final int type;
-        public final String packageName;
+        public static final String BUNDLE_FABRICATED_OVERLAY = "fabricated_overlay";
+
+        @RequestType
+        public final int type;
+        @NonNull
+        public final OverlayIdentifier overlay;
         public final int userId;
+        @Nullable
+        public final Bundle extras;
 
-        public Request(@RequestType final int type, @NonNull final String packageName,
+        public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
                 final int userId) {
+            this(type, overlay, userId, null /* extras */);
+        }
+
+        public Request(@RequestType final int type, @NonNull final OverlayIdentifier overlay,
+                final int userId, @Nullable Bundle extras) {
             this.type = type;
-            this.packageName = packageName;
+            this.overlay = overlay;
             this.userId = userId;
+            this.extras = extras;
         }
 
         @Override
         public String toString() {
-            return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}",
-                    type, typeToString(), packageName, userId);
+            return String.format(Locale.US, "Request{type=0x%02x (%s), overlay=%s, userId=%d}",
+                    type, typeToString(), overlay, userId);
         }
 
         /**
@@ -123,6 +142,8 @@
             switch (type) {
                 case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
                 case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+                case TYPE_REGISTER_FABRICATED: return "TYPE_REGISTER_FABRICATED";
+                case TYPE_UNREGISTER_FABRICATED: return "TYPE_UNREGISTER_FABRICATED";
                 default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
             }
         }
@@ -152,22 +173,56 @@
          * longer affect the resources of the target package. If the target is
          * currently running, its outdated resources will be replaced by new ones.
          *
-         * @param packageName The name of the overlay package.
+         * @param overlay The name of the overlay package.
          * @param enable true to enable the overlay, false to disable it.
          * @return this Builder object, so you can chain additional requests
          */
-        public Builder setEnabled(@NonNull String packageName, boolean enable) {
-            return setEnabled(packageName, enable, UserHandle.myUserId());
+        public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable) {
+            return setEnabled(overlay, enable, UserHandle.myUserId());
         }
 
         /**
          * @hide
          */
-        public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) {
-            checkNotNull(packageName);
+        public Builder setEnabled(@NonNull OverlayIdentifier overlay, boolean enable, int userId) {
+            checkNotNull(overlay);
             @Request.RequestType final int type =
                 enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
-            mRequests.add(new Request(type, packageName, userId));
+            mRequests.add(new Request(type, overlay, userId));
+            return this;
+        }
+
+        /**
+         * Registers the fabricated overlay with the overlay manager so it can be enabled and
+         * disabled for any user.
+         *
+         * The fabricated overlay is initialized in a disabled state. If an overlay is re-registered
+         * the existing overlay will be replaced by the newly registered overlay and the enabled
+         * state of the overlay will be left unchanged if the target package and target overlayable
+         * have not changed.
+         *
+         * @param overlay the overlay to register with the overlay manager
+         *
+         * @hide
+         */
+        public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+            final Bundle extras = new Bundle();
+            extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
+            mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
+                    UserHandle.USER_ALL, extras));
+            return this;
+        }
+
+        /**
+         * Disables and removes the overlay from the overlay manager for all users.
+         *
+         * @param overlay the overlay to disable and remove
+         *
+         * @hide
+         */
+        public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+            mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
+                    UserHandle.USER_ALL));
             return this;
         }
 
@@ -195,8 +250,9 @@
         for (int i = 0; i < size; i++) {
             final Request req = mRequests.get(i);
             dest.writeInt(req.type);
-            dest.writeString(req.packageName);
+            dest.writeParcelable(req.overlay, flags);
             dest.writeInt(req.userId);
+            dest.writeBundle(req.extras);
         }
     }
 
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 85549d8..ebe202b 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -273,7 +273,7 @@
                 text, 0, null, disabledMessage, 0, null,
                 categoriesSet, intents, rank, extras,
                 getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons, locusId);
+                disabledReason, persons, locusId, 0);
     }
 
     /** @hide */
diff --git a/core/java/android/content/pm/DataLoaderManager.java b/core/java/android/content/pm/DataLoaderManager.java
index e8fb241..4d79936 100644
--- a/core/java/android/content/pm/DataLoaderManager.java
+++ b/core/java/android/content/pm/DataLoaderManager.java
@@ -41,6 +41,7 @@
      * @param dataLoaderId ID for the new data loader binder service.
      * @param params       DataLoaderParamsParcel object that contains data loader params, including
      *                     its package name, class name, and additional parameters.
+     * @param bindDelayMs  introduce a delay before actual bind in case we want to avoid busylooping
      * @param listener     Callback for the data loader service to report status back to the
      *                     caller.
      * @return false if 1) target ID collides with a data loader that is already bound to data
@@ -48,9 +49,9 @@
      * or 4) fails to bind to the specified data loader service, otherwise return true.
      */
     public boolean bindToDataLoader(int dataLoaderId, @NonNull DataLoaderParamsParcel params,
-            @NonNull IDataLoaderStatusListener listener) {
+            long bindDelayMs, @NonNull IDataLoaderStatusListener listener) {
         try {
-            return mService.bindToDataLoader(dataLoaderId, params, listener);
+            return mService.bindToDataLoader(dataLoaderId, params, bindDelayMs, listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/IDataLoaderManager.aidl b/core/java/android/content/pm/IDataLoaderManager.aidl
index 93b3de7..dda4d36 100644
--- a/core/java/android/content/pm/IDataLoaderManager.aidl
+++ b/core/java/android/content/pm/IDataLoaderManager.aidl
@@ -23,7 +23,7 @@
 
 /** @hide */
 interface IDataLoaderManager {
-    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params,
+    boolean bindToDataLoader(int id, in DataLoaderParamsParcel params, long bindDelayMs,
             IDataLoaderStatusListener listener);
     IDataLoader getDataLoader(int dataLoaderId);
     void unbindFromDataLoader(int dataLoaderId);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a46876e..7fe2a41 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -808,4 +808,6 @@
 
     PackageManager.Property getProperty(String propertyName, String packageName, String className);
     ParceledListSlice queryProperty(String propertyName, int componentType);
+
+    void setKeepUninstalledPackages(in List<String> packageList);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b95b991b..a3c3500 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,8 +47,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -3592,9 +3592,12 @@
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
      * the requisite kernel support to support incremental delivery aka Incremental FileSystem.
      *
-     * @see IncrementalManager#isEnabled
+     * @see IncrementalManager#isFeatureEnabled
      * @hide
+     *
+     * @deprecated Use {@link #FEATURE_INCREMENTAL_DELIVERY_VERSION} instead.
      */
+    @Deprecated
     @SystemApi
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_INCREMENTAL_DELIVERY =
@@ -3602,6 +3605,20 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * feature not present - IncFs is not present on the device.
+     * 1 - IncFs v1, core features, no PerUid support. Optional in R.
+     * 2 - IncFs v2, PerUid support, fs-verity support. Required in S.
+     *
+     * @see IncrementalManager#isFeatureEnabled and IncrementalManager#isV2()
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION =
+            "android.software.incremental_delivery_version";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device has tuner hardware to support tuner operations.
      *
      * <p>This feature implies that the device has the tuner HAL implementation.
@@ -9282,4 +9299,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set a list of apps to keep around as APKs even if no user has currently installed it.
+     * @param packageList List of package names to keep cached.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES)
+    @TestApi
+    public void setKeepUninstalledPackages(@NonNull List<String> packageList) {
+        try {
+            ActivityThread.getPackageManager().setKeepUninstalledPackages(packageList);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0819d17..bf8d1f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6151,6 +6151,56 @@
         }
 
         /**
+         * Returns whether this instance is currently signed, or has ever been signed, with a
+         * signing certificate from the provided {@link Set} of {@code certDigests}.
+         *
+         * <p>The provided {@code certDigests} should contain the SHA-256 digest of the DER encoding
+         * of each trusted certificate with the digest characters in upper case. If this instance
+         * has multiple signers then all signers must be in the provided {@code Set}. If this
+         * instance has a signing lineage then this method will return true if any of the previous
+         * signers in the lineage match one of the entries in the {@code Set}.
+         */
+        public boolean hasAncestorOrSelfWithDigest(Set<String> certDigests) {
+            if (this == UNKNOWN || certDigests == null || certDigests.size() == 0) {
+                return false;
+            }
+            // If an app is signed by multiple signers then all of the signers must be in the Set.
+            if (signatures.length > 1) {
+                // If the Set has less elements than the number of signatures then immediately
+                // return false as there's no way to satisfy the requirement of all signatures being
+                // in the Set.
+                if (certDigests.size() < signatures.length) {
+                    return false;
+                }
+                for (Signature signature : signatures) {
+                    String signatureDigest = PackageUtils.computeSha256Digest(
+                            signature.toByteArray());
+                    if (!certDigests.contains(signatureDigest)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            String signatureDigest = PackageUtils.computeSha256Digest(signatures[0].toByteArray());
+            if (certDigests.contains(signatureDigest)) {
+                return true;
+            }
+            if (hasPastSigningCertificates()) {
+                // The last element in the pastSigningCertificates array is the current signer;
+                // since that was verified above just check all the signers in the lineage.
+                for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+                    signatureDigest = PackageUtils.computeSha256Digest(
+                            pastSigningCertificates[i].toByteArray());
+                    if (certDigests.contains(signatureDigest)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
          * Returns the SigningDetails with a descendant (or same) signer after verifying the
          * descendant has the same, a superset, or a subset of the lineage of the ancestor.
          *
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 35f02a8..0e70a3e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -28,8 +28,12 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
 
 /**
  * Information you can retrieve about a particular security permission
@@ -278,6 +282,15 @@
     @SystemApi
     public static final int PROTECTION_FLAG_ROLE = 0x4000000;
 
+    /**
+     * Additional flag for {@link #protectionLevel}, correspoinding to the {@code knownSigner} value
+     * of {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_KNOWN_SIGNER = 0x8000000;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
             PROTECTION_FLAG_PRIVILEGED,
@@ -303,6 +316,7 @@
             PROTECTION_FLAG_RETAIL_DEMO,
             PROTECTION_FLAG_RECENTS,
             PROTECTION_FLAG_ROLE,
+            PROTECTION_FLAG_KNOWN_SIGNER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -466,6 +480,17 @@
      */
     public @Nullable CharSequence nonLocalizedDescription;
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
+    /**
+     * A {@link Set} of trusted signing certificate digests. If this permission has the {@link
+     * #PROTECTION_FLAG_KNOWN_SIGNER} flag set the permission will be granted to a requesting app
+     * if the app is signed by any of these certificates.
+     *
+     * @hide
+     */
+    public @Nullable Set<String> knownCerts;
+
     /** @hide */
     public static int fixProtectionLevel(int level) {
         if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -570,6 +595,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_ROLE) != 0) {
             protLevel.append("|role");
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
+            protLevel.append("|knownSigner");
+        }
         return protLevel.toString();
     }
 
@@ -665,6 +693,7 @@
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+        sForStringSet.parcel(knownCerts, dest, parcelableFlags);
     }
 
     /** @hide */
@@ -730,5 +759,6 @@
         descriptionRes = source.readInt();
         requestRes = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        knownCerts = sForStringSet.unparcel(source);
     }
 }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 522f4ca..ce0547f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -434,6 +434,8 @@
 
     private int mDisabledReason;
 
+    private int mStartingThemeResId;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -462,6 +464,7 @@
         mLocusId = b.mLocusId;
 
         updateTimestamp();
+        mStartingThemeResId = b.mStartingThemeResId;
     }
 
     /**
@@ -608,6 +611,7 @@
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
         }
+        mStartingThemeResId = source.mStartingThemeResId;
     }
 
     /**
@@ -931,6 +935,9 @@
         if (source.mLocusId != null) {
             mLocusId = source.mLocusId;
         }
+        if (source.mStartingThemeResId != 0) {
+            mStartingThemeResId = source.mStartingThemeResId;
+        }
     }
 
     /**
@@ -1000,6 +1007,8 @@
 
         private LocusId mLocusId;
 
+        private int mStartingThemeResId;
+
         /**
          * Old style constructor.
          * @hide
@@ -1102,6 +1111,15 @@
         }
 
         /**
+         * Sets a theme resource id for the splash screen.
+         */
+        @NonNull
+        public Builder setStartingTheme(int themeResId) {
+            mStartingThemeResId = themeResId;
+            return this;
+        }
+
+        /**
          * @hide We don't support resource strings for dynamic shortcuts for now.  (But unit tests
          * use it.)
          */
@@ -1420,6 +1438,14 @@
         return mIcon;
     }
 
+    /**
+     * Returns the theme resource id used for the splash screen.
+     * @hide
+     */
+    public int getStartingThemeResId() {
+        return mStartingThemeResId;
+    }
+
     /** @hide -- old signature, the internal code still uses it. */
     @Nullable
     @Deprecated
@@ -2138,6 +2164,7 @@
         mPersons = source.readParcelableArray(cl, Person.class);
         mLocusId = source.readParcelable(cl);
         mIconUri = source.readString8();
+        mStartingThemeResId = source.readInt();
     }
 
     @Override
@@ -2189,6 +2216,7 @@
         dest.writeParcelableArray(mPersons, flags);
         dest.writeParcelable(mLocusId, flags);
         dest.writeString8(mIconUri);
+        dest.writeInt(mStartingThemeResId);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2345,6 +2373,12 @@
         sb.append("disabledReason=");
         sb.append(getDisabledReasonDebugString(mDisabledReason));
 
+        if (mStartingThemeResId != 0) {
+            addIndentOrComma(sb, indent);
+            sb.append("SplashScreenThemeResId=");
+            sb.append(Integer.toHexString(mStartingThemeResId));
+        }
+
         addIndentOrComma(sb, indent);
 
         sb.append("categories=");
@@ -2430,7 +2464,7 @@
             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
             long lastChangedTimestamp,
             int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
-            int disabledReason, Person[] persons, LocusId locusId) {
+            int disabledReason, Person[] persons, LocusId locusId, int startingThemeResId) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -2459,5 +2493,6 @@
         mDisabledReason = disabledReason;
         mPersons = persons;
         mLocusId = locusId;
+        mStartingThemeResId = startingThemeResId;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index c62767e..233abf3 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -71,6 +71,13 @@
     public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
+    /**
+     * Get the theme res ID of the starting window, it can be 0 if not specified.
+     */
+    public abstract int getShortcutStartingThemeResId(int launcherUserId,
+            @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId,
+            int userId);
+
     public abstract ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
             @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index bf35c4d..bc5d14a 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -22,17 +22,26 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.jar.StrictJarFile;
+import android.util.JsonReader;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.zip.ZipEntry;
 
 /**
  * Helper class used to compute and validate the location of dex metadata files.
@@ -40,6 +49,12 @@
  * @hide
  */
 public class DexMetadataHelper {
+    public static final String TAG = "DexMetadataHelper";
+    /** $> adb shell 'setprop log.tag.DexMetadataHelper VERBOSE' */
+    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    /** $> adb shell 'setprop pm.dexopt.dm.require_manifest true' */
+    private static String PROPERTY_DM_JSON_MANIFEST_REQUIRED = "pm.dexopt.dm.require_manifest";
+
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
 
     private DexMetadataHelper() {}
@@ -147,14 +162,31 @@
 
     /**
      * Validate that the given file is a dex metadata archive.
-     * This is just a validation that the file is a zip archive.
+     * This is just a validation that the file is a zip archive that contains a manifest.json
+     * with the package name and version code.
      *
      * @throws PackageParserException if the file is not a .dm file.
      */
-    public static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+    public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode)
+            throws PackageParserException {
+        validateDexMetadataFile(dmaPath, packageName, versionCode,
+               SystemProperties.getBoolean(PROPERTY_DM_JSON_MANIFEST_REQUIRED, false));
+    }
+
+    @VisibleForTesting
+    public static void validateDexMetadataFile(String dmaPath, String packageName, long versionCode,
+            boolean requireManifest) throws PackageParserException {
         StrictJarFile jarFile = null;
+
+        if (DEBUG) {
+            Log.v(TAG, "validateDexMetadataFile: " + dmaPath + ", " + packageName +
+                    ", " + versionCode);
+        }
+
         try {
             jarFile = new StrictJarFile(dmaPath, false, false);
+            validateDexMetadataManifest(dmaPath, jarFile, packageName, versionCode,
+                    requireManifest);
         } catch (IOException e) {
             throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
                     "Error opening " + dmaPath, e);
@@ -168,6 +200,72 @@
         }
     }
 
+    /** Ensure that packageName and versionCode match the manifest.json in the .dm file */
+    private static void validateDexMetadataManifest(String dmaPath, StrictJarFile jarFile,
+            String packageName, long versionCode, boolean requireManifest)
+            throws IOException, PackageParserException {
+        if (!requireManifest) {
+            if (DEBUG) {
+                Log.v(TAG, "validateDexMetadataManifest: " + dmaPath
+                        + " manifest.json check skipped");
+            }
+            return;
+        }
+
+        ZipEntry zipEntry = jarFile.findEntry("manifest.json");
+        if (zipEntry == null) {
+              throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                      "Missing manifest.json in " + dmaPath);
+        }
+        InputStream inputStream = jarFile.getInputStream(zipEntry);
+
+        JsonReader reader;
+        try {
+          reader = new JsonReader(new InputStreamReader(inputStream, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "Error opening manifest.json in " + dmaPath, e);
+        }
+        String jsonPackageName = null;
+        long jsonVersionCode = -1;
+
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (name.equals("packageName")) {
+                jsonPackageName = reader.nextString();
+            } else if (name.equals("versionCode")) {
+                jsonVersionCode = reader.nextLong();
+            } else {
+                reader.skipValue();
+            }
+        }
+        reader.endObject();
+
+        if (jsonPackageName == null || jsonVersionCode == -1) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath
+                    + " is missing 'packageName' and/or 'versionCode'");
+        }
+
+        if (!jsonPackageName.equals(packageName)) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath + " has invalid packageName: " + jsonPackageName
+                    + ", expected: " + packageName);
+        }
+
+        if (versionCode != jsonVersionCode) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "manifest.json in " + dmaPath + " has invalid versionCode: " + jsonVersionCode
+                    + ", expected: " + versionCode);
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, "validateDexMetadataManifest: " + dmaPath + ", " + packageName +
+                    ", " + versionCode + ": successful");
+        }
+    }
+
     /**
      * Validates that all dex metadata paths in the given list have a matching apk.
      * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index fb0d904..9a84ded 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -655,6 +655,7 @@
         pi.protectionLevel = p.getProtectionLevel();
         pi.descriptionRes = p.getDescriptionRes();
         pi.flags = p.getFlags();
+        pi.knownCerts = p.getKnownCerts();
 
         if ((flags & PackageManager.GET_META_DATA) == 0) {
             return pi;
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 66bdb9b..b7aa30f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2807,7 +2807,7 @@
      *                        limits length of the name to the {@link #MAX_FILE_NAME_SIZE}.
      * @return Success if it's valid.
      */
-    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+    public static String validateName(String name, boolean requireSeparator,
             boolean requireFilename) {
         final int N = name.length();
         boolean hasSep = false;
@@ -2828,18 +2828,28 @@
                 front = true;
                 continue;
             }
-            return input.error("bad character '" + c + "'");
+            return "bad character '" + c + "'";
         }
         if (requireFilename) {
             if (!FileUtils.isValidExtFilename(name)) {
-                return input.error("Invalid filename");
+                return "Invalid filename";
             } else if (N > MAX_FILE_NAME_SIZE) {
-                return input.error("the length of the name is greater than " + MAX_FILE_NAME_SIZE);
+                return "the length of the name is greater than " + MAX_FILE_NAME_SIZE;
             }
         }
-        return hasSep || !requireSeparator
-                ? input.success(null)
-                : input.error("must have at least one '.' separator");
+        return hasSep || !requireSeparator ? null : "must have at least one '.' separator";
+    }
+
+    /**
+     * @see #validateName(String, boolean, boolean)
+     */
+    public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator,
+            boolean requireFilename) {
+        final String errorMessage = validateName(name, requireSeparator, requireFilename);
+        if (errorMessage != null) {
+            return input.error(errorMessage);
+        }
+        return input.success(null);
     }
 
     /**
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/core/java/android/content/pm/parsing/component/ParsedPermission.java
index f99a0b1..37e0e87 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermission.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermission.java
@@ -21,14 +21,22 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
+
+import java.util.Locale;
+import java.util.Set;
 
 /** @hide */
 public class ParsedPermission extends ParsedComponent {
 
+    private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
+
     @Nullable
     String backgroundPermission;
     @Nullable
@@ -39,6 +47,8 @@
     boolean tree;
     @Nullable
     private ParsedPermissionGroup parsedPermissionGroup;
+    @Nullable
+    Set<String> knownCerts;
 
     @VisibleForTesting
     public ParsedPermission() {
@@ -81,6 +91,23 @@
         return protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE;
     }
 
+    public @Nullable Set<String> getKnownCerts() {
+        return knownCerts;
+    }
+
+    protected void setKnownCert(String knownCert) {
+        // Convert the provided digest to upper case for consistent Set membership
+        // checks when verifying the signing certificate digests of requesting apps.
+        this.knownCerts = Set.of(knownCert.toUpperCase(Locale.US));
+    }
+
+    protected void setKnownCerts(String[] knownCerts) {
+        this.knownCerts = new ArraySet<>();
+        for (String knownCert : knownCerts) {
+            this.knownCerts.add(knownCert.toUpperCase(Locale.US));
+        }
+    }
+
     public int calculateFootprint() {
         int size = getName().length();
         if (getNonLocalizedLabel() != null) {
@@ -109,6 +136,7 @@
         dest.writeInt(this.protectionLevel);
         dest.writeBoolean(this.tree);
         dest.writeParcelable(this.parsedPermissionGroup, flags);
+        sForStringSet.parcel(knownCerts, dest, flags);
     }
 
     protected ParsedPermission(Parcel in) {
@@ -121,6 +149,7 @@
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
         this.parsedPermissionGroup = in.readParcelable(boot);
+        this.knownCerts = sForStringSet.unparcel(in);
     }
 
     public static final Parcelable.Creator<ParsedPermission> CREATOR =
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index 9012b5ce..8afa70e 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -90,6 +90,38 @@
             permission.flags = sa.getInt(
                     R.styleable.AndroidManifestPermission_permissionFlags, 0);
 
+            final int knownCertsResource = sa.getResourceId(
+                    R.styleable.AndroidManifestPermission_knownCerts, 0);
+            if (knownCertsResource != 0) {
+                // The knownCerts attribute supports both a string array resource as well as a
+                // string resource for the case where the permission should only be granted to a
+                // single known signer.
+                final String resourceType = res.getResourceTypeName(knownCertsResource);
+                if (resourceType.equals("array")) {
+                    final String[] knownCerts = res.getStringArray(knownCertsResource);
+                    if (knownCerts != null) {
+                        permission.setKnownCerts(knownCerts);
+                    }
+                } else {
+                    final String knownCert = res.getString(knownCertsResource);
+                    if (knownCert != null) {
+                        permission.setKnownCert(knownCert);
+                    }
+                }
+                if (permission.knownCerts == null) {
+                    Slog.w(TAG, packageName + " defines a knownSigner permission but"
+                            + " the provided knownCerts resource is null");
+                }
+            } else {
+                // If the knownCerts resource ID is null check if the app specified a string
+                // value for the attribute representing a single trusted signer.
+                final String knownCert = sa.getString(
+                        R.styleable.AndroidManifestPermission_knownCerts);
+                if (knownCert != null) {
+                    permission.setKnownCert(knownCert);
+                }
+            }
+
             // For now only platform runtime permissions can be restricted
             if (!permission.isRuntime() || !"android".equals(permission.getPackageName())) {
                 permission.flags &= ~PermissionInfo.FLAG_HARD_RESTRICTED;
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cde7b2a..d302b0a 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -3,7 +3,6 @@
 toddke@android.com
 toddke@google.com
 patb@google.com
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
 zhanghai@google.com
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index af12536..cbb3baa 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -238,8 +238,8 @@
      * permissions must be acquired and
      * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
      *
-     * This will be combined with the verification status and other system state to determine which
-     * application is launched to handle an app link.
+     * Enabling an unverified domain will allow an application to open it, but this can only occur
+     * if no other app on the device is approved for the domain.
      *
      * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
      * @param domains     The domains to toggle the state of.
@@ -290,13 +290,15 @@
         public static final int REASON_ID_INVALID = 2;
         public static final int REASON_SET_NULL_OR_EMPTY = 3;
         public static final int REASON_UNKNOWN_DOMAIN = 4;
+        public static final int REASON_UNABLE_TO_APPROVE = 5;
 
         /** @hide */
         @IntDef({
                 REASON_ID_NULL,
                 REASON_ID_INVALID,
                 REASON_SET_NULL_OR_EMPTY,
-                REASON_UNKNOWN_DOMAIN
+                REASON_UNKNOWN_DOMAIN,
+                REASON_UNABLE_TO_APPROVE
         })
         public @interface Reason {
         }
@@ -313,6 +315,8 @@
                 case REASON_UNKNOWN_DOMAIN:
                     return "Domain set contains value that was not declared by the target package "
                             + packageName;
+                case REASON_UNABLE_TO_APPROVE:
+                    return "Domain set contains value that was owned by another package";
                 default:
                     return "Unknown failure";
             }
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
index 8d16f75..73346ef 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -17,6 +17,7 @@
 package android.content.pm.verify.domain;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Parcelable;
@@ -30,18 +31,18 @@
 import java.util.UUID;
 
 /**
- * Contains the user selection state for a package. This means all web HTTP(S) domains
- * declared by a package in its manifest, whether or not they were marked for auto
- * verification.
+ * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
+ * package in its manifest, whether or not they were marked for auto verification.
  * <p>
  * By default, all apps are allowed to automatically open links with domains that they've
- * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
- * The user can decide to disable this, disallowing the application from opening these
- * links.
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
+ * can decide to disable this, disallowing the application from opening all links. Note that the
+ * toggle affects <b>all</b> links and is not based on the verification state of the domains.
  * <p>
- * Separately, independent of this toggle, the user can choose specific domains to allow
- * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
- * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
+ * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
+ * a single application can be approved for a domain unless the applications are both approved. If
+ * another application is approved, the user will not be allowed to enable the domain.
  * <p>
  * These values can be changed through the
  * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
@@ -105,7 +106,8 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+    // /DomainVerificationUserSelection.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -216,7 +218,7 @@
 
     @Override
     @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
+    public boolean equals(@Nullable Object o) {
         // You can override field equality logic by defining either of the methods like:
         // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
         // boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -328,9 +330,9 @@
     };
 
     @DataClass.Generated(
-            time = 1611799495498L,
+            time = 1612829797220L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
             inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index abf694f..bbde8b1 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -252,9 +252,10 @@
             }
 
             if (overrideScale != 1.0f) {
-                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
                 applicationScale = overrideScale;
                 applicationInvertedScale = 1.0f / overrideScale;
+                applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
+                        * applicationInvertedScale) + .5f);
                 compatFlags |= HAS_OVERRIDE_SCALING;
             } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
@@ -519,10 +520,6 @@
         if (isScalingRequired()) {
             float invertedRatio = applicationInvertedScale;
             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
-            inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
-            inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
-            inoutConfig.smallestScreenWidthDp =
-                    (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
             inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
             inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
             final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
new file mode 100644
index 0000000..25758e9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for updating or adding a font family on the system.
+ *
+ * <p>You can update or add a font family with custom style parameters. The following example
+ * defines a font family called "roboto" using "Roboto-Regular" font file that is already available
+ * on the system by preloading or {@link FontManager#updateFontFile}.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("roboto", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()),
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * <p>You can update or add font files in the same request by calling
+ * {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+ * The following example adds "YourFont" font file and defines "your-font" font family in the same
+ * request. In this case, the font file represented by {@code yourFontFd} should be an OpenType
+ * compliant font file and have "YourFont" as PostScript name (ID=6) in 'name' table.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFileUpdateRequest(new FontFileUpdateRequest(yourFontFd, signature))
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("your-font", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "YourFont",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFamilyUpdateRequest {
+
+    /**
+     * A font family definition.
+     */
+    public static final class FontFamily {
+        @NonNull
+        private final String mName;
+        @NonNull
+        private final List<Font> mFonts;
+
+        /**
+         * Constructs a FontFamily.
+         *
+         * <p>A font family has a name to identify the font family. Apps can use
+         * {@link android.graphics.Typeface#create(String, int)} or XML resources to use a specific
+         * font family.
+         *
+         * <p>A font family consists of multiple fonts with different styles. The style information
+         * can be specified by {@link Font}.
+         *
+         * @see android.graphics.Typeface#create(String, int)
+         * @see Font
+         */
+        public FontFamily(@NonNull String name, @NonNull List<Font> fonts) {
+            Objects.requireNonNull(name);
+            Preconditions.checkStringNotEmpty(name);
+            Objects.requireNonNull(fonts);
+            Preconditions.checkCollectionElementsNotNull(fonts, "fonts");
+            Preconditions.checkCollectionNotEmpty(fonts, "fonts");
+            mName = name;
+            mFonts = fonts;
+        }
+
+        /**
+         * Returns the name of this family.
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the fonts in this family.
+         */
+        @NonNull
+        public List<Font> getFonts() {
+            return mFonts;
+        }
+    }
+
+    /**
+     * A single entry in a font family representing a font.
+     */
+    public static final class Font {
+
+        @NonNull
+        private final String mPostScriptName;
+        @NonNull
+        private final FontStyle mStyle;
+        @NonNull
+        private final List<FontVariationAxis> mAxes;
+
+        /**
+         * Constructs a FontStyleVariation.
+         *
+         * <p>A font has a PostScript name to identify the font file to use, a {@link FontStyle}
+         * to specify the style, and a list of {@link FontVariationAxis} to specify axis tags and
+         * values for variable fonts. If the font file identified by {@code postScriptName} is not a
+         * variable font, {@code axes} must be empty.
+         *
+         * @param postScriptName The PostScript name of the font file to use. PostScript name is in
+         *                       Name ID 6 field in 'name' table, as specified by OpenType
+         *                       specification.
+         * @param style          The style for this font.
+         * @param axes           A list of {@link FontVariationAxis} to specify axis tags and values
+         *                       for variable fonts.
+         */
+        public Font(@NonNull String postScriptName, @NonNull FontStyle style,
+                @NonNull List<FontVariationAxis> axes) {
+            Objects.requireNonNull(postScriptName);
+            Preconditions.checkStringNotEmpty(postScriptName);
+            Objects.requireNonNull(style);
+            Objects.requireNonNull(axes);
+            Preconditions.checkCollectionElementsNotNull(axes, "axes");
+            mPostScriptName = postScriptName;
+            mStyle = style;
+            mAxes = axes;
+        }
+
+        /**
+         * Returns PostScript name of the font file to use.
+         */
+        @NonNull
+        public String getPostScriptName() {
+            return mPostScriptName;
+        }
+
+        /**
+         * Returns the style.
+         */
+        @NonNull
+        public FontStyle getStyle() {
+            return mStyle;
+        }
+
+        /**
+         * Returns the list of {@link FontVariationAxis}.
+         */
+        @NonNull
+        public List<FontVariationAxis> getAxes() {
+            return mAxes;
+        }
+    }
+
+    /**
+     * Builds a {@link FontFamilyUpdateRequest}.
+     */
+    public static final class Builder {
+        @NonNull
+        private final List<FontFileUpdateRequest> mFontFileUpdateRequests = new ArrayList<>();
+        @NonNull
+        private final List<FontFamily> mFontFamilies = new ArrayList<>();
+
+        /**
+         * Constructs a FontFamilyUpdateRequest.Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Adds a {@link FontFileUpdateRequest} to execute as a part of the constructed
+         * {@link FontFamilyUpdateRequest}.
+         *
+         * @param request A font file update request.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFileUpdateRequest(@NonNull FontFileUpdateRequest request) {
+            Objects.requireNonNull(request);
+            mFontFileUpdateRequests.add(request);
+            return this;
+        }
+
+        /**
+         * Adds a font family to update an existing font family in the system font config or
+         * add as a new font family to the system font config.
+         *
+         * @param fontFamily An font family definition to add or update.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFamily(@NonNull FontFamily fontFamily) {
+            Objects.requireNonNull(fontFamily);
+            mFontFamilies.add(fontFamily);
+            return this;
+        }
+
+        /**
+         * Builds a {@link FontFamilyUpdateRequest}.
+         */
+        @NonNull
+        public FontFamilyUpdateRequest build() {
+            return new FontFamilyUpdateRequest(mFontFileUpdateRequests, mFontFamilies);
+        }
+    }
+
+    @NonNull
+    private final List<FontFileUpdateRequest> mFontFiles;
+
+    @NonNull
+    private final List<FontFamily> mFontFamilies;
+
+    private FontFamilyUpdateRequest(@NonNull List<FontFileUpdateRequest> fontFiles,
+            @NonNull List<FontFamily> fontFamilies) {
+        mFontFiles = fontFiles;
+        mFontFamilies = fontFamilies;
+    }
+
+    /**
+     * Returns the list of {@link FontFileUpdateRequest} that will be executed as a part of this
+     * request.
+     */
+    @NonNull
+    public List<FontFileUpdateRequest> getFontFileUpdateRequests() {
+        return mFontFiles;
+    }
+
+    /**
+     * Returns the list of {@link FontFamily} that will be updated in this request.
+     */
+    @NonNull
+    public List<FontFamily> getFontFamilies() {
+        return mFontFamilies;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
new file mode 100644
index 0000000..cf1dca9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Objects;
+
+/**
+ * Request for updating a font file on the system.
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFileUpdateRequest {
+
+    private final ParcelFileDescriptor mParcelFileDescriptor;
+    private final byte[] mSignature;
+
+    /**
+     * Creates a FontFileUpdateRequest with the given file and signature.
+     *
+     * @param parcelFileDescriptor A file descriptor of the font file.
+     * @param signature            A PKCS#7 detached signature for verifying the font file.
+     */
+    public FontFileUpdateRequest(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @NonNull byte[] signature) {
+        Objects.requireNonNull(parcelFileDescriptor);
+        Objects.requireNonNull(signature);
+        mParcelFileDescriptor = parcelFileDescriptor;
+        mSignature = signature;
+    }
+
+    /**
+     * Returns the file descriptor of the font file.
+     */
+    @NonNull
+    public ParcelFileDescriptor getParcelFileDescriptor() {
+        return mParcelFileDescriptor;
+    }
+
+    /**
+     * Returns the PKCS#7 detached signature for verifying the font file.
+     */
+    @NonNull
+    public byte[] getSignature() {
+        return mSignature;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index abb4f9f..f087dd0 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -35,6 +35,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -58,7 +60,7 @@
                     RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH,
                     RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME,
                     RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG,
-                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION })
+                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_FONT_NOT_FOUND })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -131,9 +133,10 @@
     public static final int RESULT_ERROR_VERSION_MISMATCH = -8;
 
     /**
-     * Indicates a failure due to IPC communication.
+     * Indicates a failure occurred because a font with the specified PostScript name could not be
+     * found.
      */
-    public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9;
+    public static final int RESULT_ERROR_FONT_NOT_FOUND = -9;
 
     /**
      * Indicates a failure of opening font file.
@@ -253,7 +256,6 @@
      * @see #RESULT_ERROR_DOWNGRADING
      * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
      * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
-     * @see #RESULT_ERROR_REMOTE_EXCEPTION
      */
     @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
             @NonNull ParcelFileDescriptor pfd,
@@ -261,10 +263,69 @@
             @IntRange(from = 0) int baseVersion
     ) {
         try {
-            return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+            return mIFontManager.updateFontFile(new FontUpdateRequest(pfd, signature), baseVersion);
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to call updateFont API", e);
-            return RESULT_ERROR_REMOTE_EXCEPTION;
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Update or add system wide font families.
+     *
+     * <p>This method will update existing font families or add new font families. The updated
+     * font family definitions will be used when creating {@link android.graphics.Typeface} objects
+     * with using {@link android.graphics.Typeface#create(String, int)} specifying the family name,
+     * or through XML resources. Note that system fallback fonts cannot be modified by this method.
+     * Apps must use {@link android.graphics.Typeface.CustomFallbackBuilder} to use custom fallback
+     * fonts.
+     *
+     * <p>Font files can be updated by including {@link FontFileUpdateRequest} to {@code request}
+     * via {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+     * The same constraints as {@link #updateFontFile} will apply when updating font files.
+     *
+     * <p>The caller must specify the base config version for keeping the font configuration
+     * consistent. If the font configuration is updated for some reason between the time you get
+     * a configuration with {@link #getFontConfig()} and the time when you call this method,
+     * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by
+     * calling {@link #getFontConfig()} and call this method again with the latest config version.
+     *
+     * @param request A {@link FontFamilyUpdateRequest} to execute.
+     * @param baseVersion A base config version to be updated. You can get the latest config version
+     *                    by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If
+     *                    the system has a newer config version, the update will fail with
+     *                    {@link #RESULT_ERROR_VERSION_MISMATCH}.
+     * @return A result code.
+     * @see FontConfig#getConfigVersion()
+     * @see #getFontConfig()
+     * @see #RESULT_SUCCESS
+     * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE
+     * @see #RESULT_ERROR_VERIFICATION_FAILURE
+     * @see #RESULT_ERROR_VERSION_MISMATCH
+     * @see #RESULT_ERROR_INVALID_FONT_FILE
+     * @see #RESULT_ERROR_INVALID_FONT_NAME
+     * @see #RESULT_ERROR_DOWNGRADING
+     * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
+     * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
+     * @see #RESULT_ERROR_FONT_NOT_FOUND
+     */
+    @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFamily(
+            @NonNull FontFamilyUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+        List<FontUpdateRequest> requests = new ArrayList<>();
+        List<FontFileUpdateRequest> fontFileUpdateRequests = request.getFontFileUpdateRequests();
+        for (int i = 0; i < fontFileUpdateRequests.size(); i++) {
+            FontFileUpdateRequest fontFile = fontFileUpdateRequests.get(i);
+            requests.add(new FontUpdateRequest(fontFile.getParcelFileDescriptor(),
+                    fontFile.getSignature()));
+        }
+        List<FontFamilyUpdateRequest.FontFamily> fontFamilies = request.getFontFamilies();
+        for (int i = 0; i < fontFamilies.size(); i++) {
+            FontFamilyUpdateRequest.FontFamily fontFamily = fontFamilies.get(i);
+            requests.add(new FontUpdateRequest(fontFamily.getName(), fontFamily.getFonts()));
+        }
+        try {
+            return mIFontManager.updateFontFamily(requests, baseVersion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index db047f8..b79c8f62 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -16,18 +16,37 @@
 
 package android.graphics.fonts;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.text.FontConfig;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents a font update request. Currently only font install request is supported.
  * @hide
  */
-// TODO: Support font config update.
 public final class FontUpdateRequest implements Parcelable {
 
+    public static final int TYPE_UPDATE_FONT_FILE = 0;
+    public static final int TYPE_UPDATE_FONT_FAMILY = 1;
+
+    @IntDef(prefix = "TYPE_", value = {
+            TYPE_UPDATE_FONT_FILE,
+            TYPE_UPDATE_FONT_FAMILY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
     public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
         @Override
         public FontUpdateRequest createFromParcel(Parcel in) {
@@ -40,39 +59,87 @@
         }
     };
 
-    @NonNull
+    private final @Type int mType;
+    // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+    @Nullable
     private final ParcelFileDescriptor mFd;
-    @NonNull
+    // NonNull if mType == TYPE_UPDATE_FONT_FILE.
+    @Nullable
     private final byte[] mSignature;
+    // NonNull if mType == TYPE_UPDATE_FONT_FAMILY.
+    @Nullable
+    private final FontConfig.FontFamily mFontFamily;
 
     public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
+        mType = TYPE_UPDATE_FONT_FILE;
         mFd = fd;
         mSignature = signature;
+        mFontFamily = null;
     }
 
-    private FontUpdateRequest(Parcel in) {
+    public FontUpdateRequest(@NonNull FontConfig.FontFamily fontFamily) {
+        mType = TYPE_UPDATE_FONT_FAMILY;
+        mFd = null;
+        mSignature = null;
+        mFontFamily = fontFamily;
+    }
+
+    public FontUpdateRequest(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> variations) {
+        // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily.
+        this(createFontFamily(postScriptName, variations));
+    }
+
+    private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> fonts) {
+        List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size());
+        for (FontFamilyUpdateRequest.Font font : fonts) {
+            // TODO: Support .otf.
+            configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null,
+                    font.getStyle(), 0 /* index */,
+                    FontVariationAxis.toFontVariationSettings(font.getAxes()),
+                    null /* fontFamilyName */));
+        }
+        return new FontConfig.FontFamily(configFonts, postScriptName,
+                LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+    }
+
+    protected FontUpdateRequest(Parcel in) {
+        mType = in.readInt();
         mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
         mSignature = in.readBlob();
+        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
     }
 
-    @NonNull
+    public @Type int getType() {
+        return mType;
+    }
+
+    @Nullable
     public ParcelFileDescriptor getFd() {
         return mFd;
     }
 
-    @NonNull
+    @Nullable
     public byte[] getSignature() {
         return mSignature;
     }
 
+    @Nullable
+    public FontConfig.FontFamily getFontFamily() {
+        return mFontFamily;
+    }
+
     @Override
     public int describeContents() {
-        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        return mFd != null ? mFd.describeContents() : 0;
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
         dest.writeParcelable(mFd, flags);
         dest.writeBlob(mSignature);
+        dest.writeParcelable(mFontFamily, flags);
     }
 }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 08b1e24..5f5697a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -236,7 +236,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 2b68989..1c35608 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -25,6 +25,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Log;
 
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -33,7 +34,10 @@
  */
 @TestApi
 public class BiometricTestSession implements AutoCloseable {
+    private static final String TAG = "BiometricTestSession";
+
     private final Context mContext;
+    private final int mSensorId;
     private final ITestSession mTestSession;
 
     // Keep track of users that were tested, which need to be cleaned up when finishing.
@@ -42,8 +46,10 @@
     /**
      * @hide
      */
-    public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+    public BiometricTestSession(@NonNull Context context, int sensorId,
+            @NonNull ITestSession testSession) {
         mContext = context;
+        mSensorId = sensorId;
         mTestSession = testSession;
         mTestedUsers = new ArraySet<>();
         setTestHalEnabled(true);
@@ -61,6 +67,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     private void setTestHalEnabled(boolean enabled) {
         try {
+            Log.w(TAG, "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
             mTestSession.setTestHalEnabled(enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8fe7158..8451ded 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1233,6 +1233,8 @@
                                              int sequenceId) {
             synchronized (mInterfaceLock) {
                 if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
                 }
             }
@@ -1263,7 +1265,12 @@
                     mRequestUpdatedNeeded = false;
                     resumeInternalRepeatingRequest(false);
                 } else if (mInternalRepeatingRequestEnabled) {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                     resumeInternalRepeatingRequest(true);
+                } else {
+                    mRepeatingRequestImageReader.setOnImageAvailableListener(
+                            new ImageLoopbackCallback(), mHandler);
                 }
             }
 
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 8bb0b184..10a814a 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1917,9 +1917,18 @@
             (3 << HAL_DATASPACE_TRANSFER_SHIFT) |
             (1 << HAL_DATASPACE_RANGE_SHIFT);
 
-    private static final int HAL_DATASPACE_DEPTH = 0x1000;
-    private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
-    private static final int HAL_DATASPACE_HEIF = 0x1003;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_DEPTH = 0x1000;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
+    /**
+     * @hide
+     */
+    public static final int HAL_DATASPACE_HEIF = 0x1003;
     private static final long DURATION_20FPS_NS = 50000000L;
     /**
      * @see #getDurations(int, int)
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index f175e7b..2d4b2cc 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -42,6 +42,12 @@
      */
     public static final int INVALID_DEVICE_STATE = -1;
 
+    /** The minimum allowed device state identifier. */
+    public static final int MINIMUM_DEVICE_STATE = 0;
+
+    /** The maximum allowed device state identifier. */
+    public static final int MAXIMUM_DEVICE_STATE = 255;
+
     private final DeviceStateManagerGlobal mGlobal;
 
     /** @hide */
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
similarity index 79%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/hardware/face/FaceAuthenticationFrame.aidl
index 14d57bf..4dc41f1 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2018 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,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.face;
 
-package android.app.timedetector;
-
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FaceAuthenticationFrame;
diff --git a/core/java/android/hardware/face/FaceDataFrame.java b/core/java/android/hardware/face/FaceDataFrame.java
index 3a0e09b..092359c 100644
--- a/core/java/android/hardware/face/FaceDataFrame.java
+++ b/core/java/android/hardware/face/FaceDataFrame.java
@@ -63,6 +63,22 @@
     }
 
     /**
+     * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}.
+     *
+     * @param acquiredInfo An integer corresponding to a known acquired message.
+     * @param vendorCode An integer representing a custom vendor-specific message. Ignored unless
+     *  {@code acquiredInfo} is {@code FACE_ACQUIRED_VENDOR}.
+     */
+    public FaceDataFrame(int acquiredInfo, int vendorCode) {
+        mAcquiredInfo = acquiredInfo;
+        mVendorCode = vendorCode;
+        mPan = 0f;
+        mTilt = 0f;
+        mDistance = 0f;
+        mIsCancellable = false;
+    }
+
+    /**
      * @return An integer corresponding to a known acquired message.
      *
      * @see android.hardware.biometrics.BiometricFaceConstants
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/hardware/face/FaceEnrollFrame.aidl
similarity index 80%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/hardware/face/FaceEnrollFrame.aidl
index 14d57bf..b854681 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/hardware/face/FaceEnrollFrame.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2018 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,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.hardware.face;
 
-package android.app.timedetector;
-
-parcelable ExternalTimeSuggestion;
+/**
+ * @hide
+ */
+parcelable FaceEnrollFrame;
diff --git a/core/java/android/hardware/face/FaceEnrollStage.java b/core/java/android/hardware/face/FaceEnrollStage.java
index 03dba55..de717fb 100644
--- a/core/java/android/hardware/face/FaceEnrollStage.java
+++ b/core/java/android/hardware/face/FaceEnrollStage.java
@@ -28,6 +28,7 @@
  */
 @Retention(RetentionPolicy.SOURCE)
 @IntDef({
+    FaceEnrollStage.UNKNOWN,
     FaceEnrollStage.FIRST_FRAME_RECEIVED,
     FaceEnrollStage.WAITING_FOR_CENTERING,
     FaceEnrollStage.HOLD_STILL_IN_CENTER,
@@ -37,6 +38,11 @@
 })
 public @interface FaceEnrollStage {
     /**
+     * The current enrollment stage is not known.
+     */
+    int UNKNOWN = -1;
+
+    /**
      * Enrollment has just begun. No action is needed from the user yet.
      */
     int FIRST_FRAME_RECEIVED = 0;
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 588bc01..886a8c1 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -71,17 +71,19 @@
     private static final int MSG_FACE_DETECTED = 109;
     private static final int MSG_CHALLENGE_INTERRUPTED = 110;
     private static final int MSG_CHALLENGE_INTERRUPT_FINISHED = 111;
+    private static final int MSG_AUTHENTICATION_FRAME = 112;
+    private static final int MSG_ENROLLMENT_FRAME = 113;
 
     private final IFaceService mService;
     private final Context mContext;
     private IBinder mToken = new Binder();
-    private AuthenticationCallback mAuthenticationCallback;
-    private FaceDetectionCallback mFaceDetectionCallback;
-    private EnrollmentCallback mEnrollmentCallback;
-    private RemovalCallback mRemovalCallback;
-    private SetFeatureCallback mSetFeatureCallback;
-    private GetFeatureCallback mGetFeatureCallback;
-    private GenerateChallengeCallback mGenerateChallengeCallback;
+    @Nullable private AuthenticationCallback mAuthenticationCallback;
+    @Nullable private FaceDetectionCallback mFaceDetectionCallback;
+    @Nullable private EnrollmentCallback mEnrollmentCallback;
+    @Nullable private RemovalCallback mRemovalCallback;
+    @Nullable private SetFeatureCallback mSetFeatureCallback;
+    @Nullable private GetFeatureCallback mGetFeatureCallback;
+    @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
     private Handler mHandler;
@@ -154,6 +156,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
             mHandler.obtainMessage(MSG_CHALLENGE_INTERRUPT_FINISHED, sensorId).sendToTarget();
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+            mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+        }
     };
 
     /**
@@ -821,67 +833,6 @@
     }
 
     /**
-     * @hide
-     */
-    public static String getAcquiredString(Context context, int acquireInfo, int vendorCode) {
-        switch (acquireInfo) {
-            case FACE_ACQUIRED_GOOD:
-                return null;
-            case FACE_ACQUIRED_INSUFFICIENT:
-                return context.getString(R.string.face_acquired_insufficient);
-            case FACE_ACQUIRED_TOO_BRIGHT:
-                return context.getString(R.string.face_acquired_too_bright);
-            case FACE_ACQUIRED_TOO_DARK:
-                return context.getString(R.string.face_acquired_too_dark);
-            case FACE_ACQUIRED_TOO_CLOSE:
-                return context.getString(R.string.face_acquired_too_close);
-            case FACE_ACQUIRED_TOO_FAR:
-                return context.getString(R.string.face_acquired_too_far);
-            case FACE_ACQUIRED_TOO_HIGH:
-                return context.getString(R.string.face_acquired_too_high);
-            case FACE_ACQUIRED_TOO_LOW:
-                return context.getString(R.string.face_acquired_too_low);
-            case FACE_ACQUIRED_TOO_RIGHT:
-                return context.getString(R.string.face_acquired_too_right);
-            case FACE_ACQUIRED_TOO_LEFT:
-                return context.getString(R.string.face_acquired_too_left);
-            case FACE_ACQUIRED_POOR_GAZE:
-                return context.getString(R.string.face_acquired_poor_gaze);
-            case FACE_ACQUIRED_NOT_DETECTED:
-                return context.getString(R.string.face_acquired_not_detected);
-            case FACE_ACQUIRED_TOO_MUCH_MOTION:
-                return context.getString(R.string.face_acquired_too_much_motion);
-            case FACE_ACQUIRED_RECALIBRATE:
-                return context.getString(R.string.face_acquired_recalibrate);
-            case FACE_ACQUIRED_TOO_DIFFERENT:
-                return context.getString(R.string.face_acquired_too_different);
-            case FACE_ACQUIRED_TOO_SIMILAR:
-                return context.getString(R.string.face_acquired_too_similar);
-            case FACE_ACQUIRED_PAN_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_pan_too_extreme);
-            case FACE_ACQUIRED_TILT_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_tilt_too_extreme);
-            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
-                return context.getString(R.string.face_acquired_roll_too_extreme);
-            case FACE_ACQUIRED_FACE_OBSCURED:
-                return context.getString(R.string.face_acquired_obscured);
-            case FACE_ACQUIRED_START:
-                return null;
-            case FACE_ACQUIRED_SENSOR_DIRTY:
-                return context.getString(R.string.face_acquired_sensor_dirty);
-            case FACE_ACQUIRED_VENDOR: {
-                String[] msgArray = context.getResources().getStringArray(
-                        R.array.face_acquired_vendor);
-                if (vendorCode < msgArray.length) {
-                    return msgArray[vendorCode];
-                }
-            }
-        }
-        Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
-        return null;
-    }
-
-    /**
      * Used so BiometricPrompt can map the face ones onto existing public constants.
      * @hide
      */
@@ -1248,6 +1199,12 @@
                 case MSG_CHALLENGE_INTERRUPT_FINISHED:
                     sendChallengeInterruptFinished((int) msg.obj /* sensorId */);
                     break;
+                case MSG_AUTHENTICATION_FRAME:
+                    sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+                    break;
+                case MSG_ENROLLMENT_FRAME:
+                    sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -1349,15 +1306,172 @@
 
     private void sendAcquiredResult(int acquireInfo, int vendorCode) {
         if (mAuthenticationCallback != null) {
+            final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendAuthenticationFrame(frame);
+        } else if (mEnrollmentCallback != null) {
+            final FaceEnrollFrame frame = new FaceEnrollFrame(
+                    null /* cell */,
+                    FaceEnrollStage.UNKNOWN,
+                    new FaceDataFrame(acquireInfo, vendorCode));
+            sendEnrollmentFrame(frame);
+        }
+    }
+
+    private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null authentication frame");
+        } else if (mAuthenticationCallback != null) {
+            // TODO(b/178414967): Send additional frame data to callback
+            final int acquireInfo = frame.getData().getAcquiredInfo();
+            final int vendorCode = frame.getData().getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
             mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+            // Ensure that only non-null help messages are sent.
+            if (helpMessage != null) {
+                mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+            }
         }
-        final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
-        final int clientInfo = acquireInfo == FACE_ACQUIRED_VENDOR
-                ? (vendorCode + FACE_ACQUIRED_VENDOR_BASE) : acquireInfo;
-        if (mEnrollmentCallback != null) {
-            mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
-        } else if (mAuthenticationCallback != null && msg != null) {
-            mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+    }
+
+    private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
+        if (frame == null) {
+            Slog.w(TAG, "Received null enrollment frame");
+        } else if (mEnrollmentCallback != null) {
+            // TODO(b/178414967): Send additional frame data to callback
+            final int acquireInfo = frame.getData().getAcquiredInfo();
+            final int vendorCode = frame.getData().getVendorCode();
+            final int helpCode = getHelpCode(acquireInfo, vendorCode);
+            final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
+            mEnrollmentCallback.onEnrollmentHelp(helpCode, helpMessage);
         }
     }
+
+    private static int getHelpCode(int acquireInfo, int vendorCode) {
+        return acquireInfo == FACE_ACQUIRED_VENDOR
+                ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+                : acquireInfo;
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static String getAuthHelpMessage(Context context, int acquireInfo, int vendorCode) {
+        switch (acquireInfo) {
+            // No help message is needed for a good capture.
+            case FACE_ACQUIRED_GOOD:
+            case FACE_ACQUIRED_START:
+                return null;
+
+            // Consolidate positional feedback to reduce noise during authentication.
+            case FACE_ACQUIRED_NOT_DETECTED:
+            case FACE_ACQUIRED_TOO_CLOSE:
+            case FACE_ACQUIRED_TOO_FAR:
+            case FACE_ACQUIRED_TOO_HIGH:
+            case FACE_ACQUIRED_TOO_LOW:
+            case FACE_ACQUIRED_TOO_RIGHT:
+            case FACE_ACQUIRED_TOO_LEFT:
+            case FACE_ACQUIRED_POOR_GAZE:
+            case FACE_ACQUIRED_PAN_TOO_EXTREME:
+            case FACE_ACQUIRED_TILT_TOO_EXTREME:
+            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_not_detected);
+
+            // Provide more detailed feedback for other soft errors.
+            case FACE_ACQUIRED_INSUFFICIENT:
+                return context.getString(R.string.face_acquired_insufficient);
+            case FACE_ACQUIRED_TOO_BRIGHT:
+                return context.getString(R.string.face_acquired_too_bright);
+            case FACE_ACQUIRED_TOO_DARK:
+                return context.getString(R.string.face_acquired_too_dark);
+            case FACE_ACQUIRED_TOO_MUCH_MOTION:
+                return context.getString(R.string.face_acquired_too_much_motion);
+            case FACE_ACQUIRED_RECALIBRATE:
+                return context.getString(R.string.face_acquired_recalibrate);
+            case FACE_ACQUIRED_TOO_DIFFERENT:
+                return context.getString(R.string.face_acquired_too_different);
+            case FACE_ACQUIRED_TOO_SIMILAR:
+                return context.getString(R.string.face_acquired_too_similar);
+            case FACE_ACQUIRED_FACE_OBSCURED:
+                return context.getString(R.string.face_acquired_obscured);
+            case FACE_ACQUIRED_SENSOR_DIRTY:
+                return context.getString(R.string.face_acquired_sensor_dirty);
+
+            // Find and return the appropriate vendor-specific message.
+            case FACE_ACQUIRED_VENDOR: {
+                String[] msgArray = context.getResources().getStringArray(
+                        R.array.face_acquired_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+
+        Slog.w(TAG, "Unknown authentication acquired message: " + acquireInfo + ", " + vendorCode);
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @Nullable
+    public static String getEnrollHelpMessage(Context context, int acquireInfo, int vendorCode) {
+        switch (acquireInfo) {
+            case FACE_ACQUIRED_GOOD:
+            case FACE_ACQUIRED_START:
+                return null;
+            case FACE_ACQUIRED_INSUFFICIENT:
+                return context.getString(R.string.face_acquired_insufficient);
+            case FACE_ACQUIRED_TOO_BRIGHT:
+                return context.getString(R.string.face_acquired_too_bright);
+            case FACE_ACQUIRED_TOO_DARK:
+                return context.getString(R.string.face_acquired_too_dark);
+            case FACE_ACQUIRED_TOO_CLOSE:
+                return context.getString(R.string.face_acquired_too_close);
+            case FACE_ACQUIRED_TOO_FAR:
+                return context.getString(R.string.face_acquired_too_far);
+            case FACE_ACQUIRED_TOO_HIGH:
+                return context.getString(R.string.face_acquired_too_high);
+            case FACE_ACQUIRED_TOO_LOW:
+                return context.getString(R.string.face_acquired_too_low);
+            case FACE_ACQUIRED_TOO_RIGHT:
+                return context.getString(R.string.face_acquired_too_right);
+            case FACE_ACQUIRED_TOO_LEFT:
+                return context.getString(R.string.face_acquired_too_left);
+            case FACE_ACQUIRED_POOR_GAZE:
+                return context.getString(R.string.face_acquired_poor_gaze);
+            case FACE_ACQUIRED_NOT_DETECTED:
+                return context.getString(R.string.face_acquired_not_detected);
+            case FACE_ACQUIRED_TOO_MUCH_MOTION:
+                return context.getString(R.string.face_acquired_too_much_motion);
+            case FACE_ACQUIRED_RECALIBRATE:
+                return context.getString(R.string.face_acquired_recalibrate);
+            case FACE_ACQUIRED_TOO_DIFFERENT:
+                return context.getString(R.string.face_acquired_too_different);
+            case FACE_ACQUIRED_TOO_SIMILAR:
+                return context.getString(R.string.face_acquired_too_similar);
+            case FACE_ACQUIRED_PAN_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_pan_too_extreme);
+            case FACE_ACQUIRED_TILT_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_tilt_too_extreme);
+            case FACE_ACQUIRED_ROLL_TOO_EXTREME:
+                return context.getString(R.string.face_acquired_roll_too_extreme);
+            case FACE_ACQUIRED_FACE_OBSCURED:
+                return context.getString(R.string.face_acquired_obscured);
+            case FACE_ACQUIRED_SENSOR_DIRTY:
+                return context.getString(R.string.face_acquired_sensor_dirty);
+            case FACE_ACQUIRED_VENDOR: {
+                String[] msgArray = context.getResources().getStringArray(
+                        R.array.face_acquired_vendor);
+                if (vendorCode < msgArray.length) {
+                    return msgArray[vendorCode];
+                }
+            }
+        }
+        Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
+        return null;
+    }
 }
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index bd4d3a0..2ef1430 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -16,6 +16,8 @@
 package android.hardware.face;
 
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 
 /**
  * Communication channel from the FaceService back to FaceAuthenticationManager.
@@ -34,4 +36,6 @@
     void onChallengeGenerated(int sensorId, long challenge);
     void onChallengeInterrupted(int sensorId);
     void onChallengeInterruptFinished(int sensorId);
+    void onAuthenticationFrame(in FaceAuthenticationFrame frame);
+    void onEnrollmentFrame(in FaceEnrollFrame frame);
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 188a2a4..a614ebf 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -153,7 +153,7 @@
     @RequiresPermission(TEST_BIOMETRIC)
     public BiometricTestSession createTestSession(int sensorId) {
         try {
-            return new BiometricTestSession(mContext,
+            return new BiometricTestSession(mContext, sensorId,
                     mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
index f4d8a65..a4817ae 100644
--- a/core/java/android/hardware/input/InputDeviceVibrator.java
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -74,6 +74,11 @@
     }
 
     @Override
+    public int getId() {
+        return mVibratorId;
+    }
+
+    @Override
     public boolean hasVibrator() {
         return true;
     }
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
index a381b02..d843407 100644
--- a/core/java/android/hardware/input/InputDeviceVibratorManager.java
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -16,9 +16,12 @@
 
 package android.hardware.input;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.CombinedVibrationEffect;
 import android.os.NullVibrator;
+import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.util.SparseArray;
@@ -86,6 +89,7 @@
         }
     }
 
+    @NonNull
     @Override
     public int[] getVibratorIds() {
         synchronized (mVibrators) {
@@ -97,6 +101,7 @@
         }
     }
 
+    @NonNull
     @Override
     public Vibrator getVibrator(int vibratorId) {
         synchronized (mVibrators) {
@@ -107,6 +112,7 @@
         return NullVibrator.getInstance();
     }
 
+    @NonNull
     @Override
     public Vibrator getDefaultVibrator() {
         // Returns vibrator ID 0
@@ -119,7 +125,13 @@
     }
 
     @Override
-    public void vibrate(CombinedVibrationEffect effect) {
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
         mInputManager.vibrate(mDeviceId, effect, mToken);
     }
+
+    @Override
+    public void cancel() {
+        mInputManager.cancelVibrate(mDeviceId, mToken);
+    }
 }
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 43480ab..49beeb3 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -127,6 +127,20 @@
      * This function returns RESULT_SUCCESS if the message has reached the HAL, but
      * does not guarantee delivery of the message to the target nanoapp.
      *
+     * Before sending the first message to your nanoapp, it's recommended that the following
+     * operations should be performed:
+     * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is
+     *    present.
+     * 2) Validate that you have the permissions to communicate with the nanoapp by looking at
+     *    {@link NanoAppState#getNanoAppPermissions}.
+     * 3) If you don't have permissions, send an idempotent message to the nanoapp ensuring any
+     *    work your app previously may have asked it to do is stopped. This is useful if your app
+     *    restarts due to permission changes and no longer has the permissions when it is started
+     *    again.
+     * 4) If you have valid permissions, send a message to your nanoapp to resubscribe so that it's
+     *    aware you have restarted or so you can initially subscribe if this is the first time you
+     *    have sent it a message.
+     *
      * @param message the message object to send
      *
      * @return the result of sending the message defined as in ContextHubTransaction.Result
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index b31b85f..7e484dd 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -117,10 +117,11 @@
      * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
      * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
      *    At this point, any further attempts of communication between the nanoapp and the
-     *    {@link ContextHubClient} will be dropped by the contexthub along with
-     *    {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
-     *    should assume no communciation can happen again until
-     *    {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+     *    {@link ContextHubClient} will be dropped by the contexthub and a return value of
+     *    {@link ContextHubTransaction#RESULT_FAILED_PERMISSION_DENIED} will be used when calling
+     *    {@link ContextHubClient#sendMessageToNanoApp}. The {@link ContextHubClient} should assume
+     *    no communciation can happen again until {@link ContextHubManager#AUTHORIZATION_GRANTED} is
+     *    received.
      *
      * @param client the client that is associated with this callback
      * @param nanoAppId the ID of the nanoapp associated with the new
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index d11e0a9..86f77c0 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -81,7 +81,8 @@
             RESULT_FAILED_AT_HUB,
             RESULT_FAILED_TIMEOUT,
             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
-            RESULT_FAILED_HAL_UNAVAILABLE
+            RESULT_FAILED_HAL_UNAVAILABLE,
+            RESULT_FAILED_PERMISSION_DENIED
     })
     public @interface Result {}
     public static final int RESULT_SUCCESS = 0;
@@ -117,6 +118,11 @@
      * Failure mode when the Context Hub HAL was not available.
      */
     public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
+    /**
+     * Failure mode when the user of the API doesn't have the required permissions to perform the
+     * operation.
+     */
+    public static final int RESULT_FAILED_PERMISSION_DENIED = 9;
 
     /**
      * A class describing the response for a ContextHubTransaction.
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 383321b..bd40409 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -4,3 +4,6 @@
 wyattriley@google.com
 etn@google.com
 weiwa@google.com
+
+# ContextHub team
+per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index ca79901..7f07af7 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -135,6 +135,12 @@
     /* Resets the USB gadget. */
     void resetUsbGadget();
 
+    /* Set USB data on or off */
+    boolean enableUsbDataSignal(boolean enable);
+
+    /* Gets the USB Hal Version. */
+    int getUsbHalVersion();
+
     /* Get the functionfs control handle for the given function. Usb
      * descriptors will already be written, and the handle will be
      * ready to use.
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index 8f2b39d..8f5c2a0 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 175220
 
-moltmann@google.com
 badhri@google.com
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 5bac481..841fd2f 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -516,6 +516,46 @@
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
 
     /**
+     * The Value for USB hal is not presented.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_NOT_SUPPORTED = -1;
+
+    /**
+     * Value for USB Hal Version v1.0.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_0 = 10;
+
+    /**
+     * Value for USB Hal Version v1.1.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_1 = 11;
+
+    /**
+     * Value for USB Hal Version v1.2.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_2 = 12;
+
+    /**
+     * Value for USB Hal Version v1.3.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V1_3 = 13;
+
+    /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
@@ -617,6 +657,16 @@
     })
     public @interface UsbGadgetHalVersion {}
 
+    /** @hide */
+    @IntDef(prefix = { "USB_HAL_" }, value = {
+            USB_HAL_NOT_SUPPORTED,
+            USB_HAL_V1_0,
+            USB_HAL_V1_1,
+            USB_HAL_V1_2,
+            USB_HAL_V1_3,
+    })
+    public @interface UsbHalVersion {}
+
     private final Context mContext;
     private final IUsbManager mService;
 
@@ -1076,6 +1126,26 @@
     }
 
     /**
+     * Get the Current USB Hal Version.
+     * <p>
+     * This function returns the current USB Hal Version.
+     * </p>
+     *
+     * @return a integer {@code USB_HAL_*} represent hal version.
+     *
+     * {@hide}
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public @UsbHalVersion int getUsbHalVersion() {
+        try {
+            return mService.getUsbHalVersion();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets the USB Gadget.
      * <p>
      * Performs USB data stack reset through USB Gadget HAL.
@@ -1095,6 +1165,28 @@
     }
 
     /**
+     * Enable/Disable the USB data signaling.
+     * <p>
+     * Enables/Disables USB data path in all the USB ports.
+     * It will force to stop or restore USB data signaling.
+     * </p>
+     *
+     * @param enable enable or disable USB data signaling
+     * @return true enable or disable USB data successfully
+     *         false if something wrong
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public boolean enableUsbDataSignal(boolean enable) {
+        try {
+            return mService.enableUsbDataSignal(enable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns a list of physical USB ports on the device.
      * <p>
      * This list is guaranteed to contain all dual-role USB Type C ports but it might
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl
index d6774d4..933256a 100644
--- a/core/java/android/net/IIpSecService.aidl
+++ b/core/java/android/net/IIpSecService.aidl
@@ -58,6 +58,9 @@
             in LinkAddress localAddr,
             in String callingPackage);
 
+    void setNetworkForTunnelInterface(
+            int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
     void deleteTunnelInterface(int resourceId, in String callingPackage);
 
     IpSecTransformResponse createTransform(
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
index 14d57bf..7979afc 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl
@@ -1,11 +1,12 @@
-/*
+/**
+ *
  * 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
+ *     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,
@@ -14,6 +15,9 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.net;
 
-parcelable ExternalTimeSuggestion;
+/** @hide */
+oneway interface IOnSetOemNetworkPreferenceListener {
+    void onComplete();
+}
diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl
new file mode 100644
index 0000000..271efe4
--- /dev/null
+++ b/core/java/android/net/IVpnManager.aidl
@@ -0,0 +1,62 @@
+/**
+ * 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.net;
+
+import android.net.Network;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+
+/**
+ * Interface that manages VPNs.
+ */
+/** {@hide} */
+interface IVpnManager {
+    /** VpnService APIs */
+    boolean prepareVpn(String oldPackage, String newPackage, int userId);
+    void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
+    ParcelFileDescriptor establishVpn(in VpnConfig config);
+    boolean addVpnAddress(String address, int prefixLength);
+    boolean removeVpnAddress(String address, int prefixLength);
+    boolean setUnderlyingNetworksForVpn(in Network[] networks);
+
+    /** VpnManager APIs */
+    boolean provisionVpnProfile(in VpnProfile profile, String packageName);
+    void deleteVpnProfile(String packageName);
+    void startVpnProfile(String packageName);
+    void stopVpnProfile(String packageName);
+
+    /** Always-on VPN APIs */
+    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
+    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+            in List<String> lockdownAllowlist);
+    String getAlwaysOnVpnPackage(int userId);
+    boolean isVpnLockdownEnabled(int userId);
+    List<String> getVpnLockdownAllowlist(int userId);
+    boolean isCallerCurrentAlwaysOnVpnApp();
+    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
+
+    /** Legacy VPN APIs */
+    void startLegacyVpn(in VpnProfile profile);
+    LegacyVpnInfo getLegacyVpnInfo(int userId);
+    boolean updateLockdownVpn();
+
+    /** General system APIs */
+    VpnConfig getVpnConfig(int userId);
+    void factoryReset();
+}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 70bca30..98acd98 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -782,6 +782,42 @@
             }
         }
 
+        /**
+         * Update the underlying network for this IpSecTunnelInterface.
+         *
+         * <p>This new underlying network will be used for all transforms applied AFTER this call is
+         * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+         * this tunnel interface, traffic will still use the old SA, and be routed on the old
+         * underlying network.
+         *
+         * <p>To migrate IPsec tunnel mode traffic, a caller should:
+         *
+         * <ol>
+         *   <li>Update the IpSecTunnelInterface’s underlying network.
+         *   <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+         *       IpSecTunnelInterface.
+         * </ol>
+         *
+         * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+         *     This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
+         *     this method will throw an {@link IllegalArgumentException}.
+         */
+        // TODO: b/169171001 Update the documentation when transform migration is supported.
+        // The purpose of making updating network and applying transforms separate is to leave open
+        // the possibility to support lossless migration procedures. To do that, Android platform
+        // will need to support multiple inbound tunnel mode transforms, just like it can support
+        // multiple transport mode transforms.
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+        public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+            try {
+                mService.setNetworkForTunnelInterface(
+                        mResourceId, underlyingNetwork, mOpPackageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
                 @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
                 @NonNull Network underlyingNetwork)
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index e01e5ae..f7c1c4b 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -19,7 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.system.ErrnoException;
-import android.system.Int32Ref;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructLinger;
@@ -65,14 +64,11 @@
         public int available() throws IOException {
             FileDescriptor myFd = fd;
             if (myFd == null) throw new IOException("socket closed");
-
-            Int32Ref avail = new Int32Ref(0);
             try {
-                Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
+                return Os.ioctlInt(myFd, OsConstants.FIONREAD);
             } catch (ErrnoException e) {
                 throw e.rethrowAsIOException();
             }
-            return avail.value;
         }
 
         /** {@inheritDoc} */
@@ -134,7 +130,7 @@
         public void write (byte[] b) throws IOException {
             write(b, 0, b.length);
         }
-        
+
         /** {@inheritDoc} */
         @Override
         public void write (byte[] b, int off, int len) throws IOException {
@@ -255,7 +251,7 @@
     /** note timeout presently ignored */
     protected void connect(LocalSocketAddress address, int timeout)
                         throws IOException
-    {        
+    {
         if (fd == null) {
             throw new IOException("socket not created");
         }
@@ -339,7 +335,7 @@
      * @throws IOException if socket has been closed or cannot be created.
      */
     protected OutputStream getOutputStream() throws IOException
-    { 
+    {
         if (fd == null) {
             throw new IOException("socket not created");
         }
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3e6237d..6353a25 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -55,6 +56,7 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.NETWORK_POLICY_SERVICE)
 public class NetworkPolicyManager {
 
@@ -125,6 +127,7 @@
     public static final int RULE_REJECT_ALL = 1 << 6;
     /**
      * Reject traffic on all networks for restricted networking mode.
+     * @hide
      */
     public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10;
 
@@ -351,6 +354,7 @@
     }
 
     /** @hide */
+    @TestApi
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void setRestrictBackground(boolean restrictBackground) {
         try {
@@ -361,6 +365,7 @@
     }
 
     /** @hide */
+    @TestApi
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean getRestrictBackground() {
         try {
@@ -506,6 +511,8 @@
 
     /**
      * Get multipath preference for the given network.
+     *
+     * @hide
      */
     public int getMultipathPreference(Network network) {
         try {
@@ -624,7 +631,9 @@
     }
 
     /** @hide */
-    public static String resolveNetworkId(WifiConfiguration config) {
+    @TestApi
+    @NonNull
+    public static String resolveNetworkId(@NonNull WifiConfiguration config) {
         return WifiInfo.sanitizeSsid(config.isPasspoint()
                 ? config.providerFriendlyName : config.SSID);
     }
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index 5e56164..b403455 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcelable;
 
@@ -29,11 +30,12 @@
 import java.util.Objects;
 
 /** @hide */
+@SystemApi
 public final class OemNetworkPreferences implements Parcelable {
     /**
-     * Use default behavior requesting networks. Equivalent to not setting any preference at all.
+     * Default in case this value is not set. Using it will result in an error.
      */
-    public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0;
+    public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0;
 
     /**
      * If an unmetered network is available, use it.
@@ -45,17 +47,17 @@
     /**
      * If an unmetered network is available, use it.
      * Otherwise, if a network with the OEM_PAID capability is available, use it.
-     * Otherwise, the app doesn't get a network.
+     * Otherwise, the app doesn't get a default network.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PAID networks.
+     * Use only NET_CAPABILITY_OEM_PAID networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3;
 
     /**
-     * Prefer only NET_CAPABILITY_OEM_PRIVATE networks.
+     * Use only NET_CAPABILITY_OEM_PRIVATE networks.
      */
     public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4;
 
@@ -95,8 +97,6 @@
     /**
      * Builder used to create {@link OemNetworkPreferences} objects.  Specify the preferred Network
      * to package name mappings.
-     *
-     * @hide
      */
     public static final class Builder {
         private final Bundle mNetworkMappings;
@@ -135,7 +135,7 @@
          * @return The builder to facilitate chaining.
          */
         @NonNull
-        public Builder removeNetworkPreference(@NonNull final String packageName) {
+        public Builder clearNetworkPreference(@NonNull final String packageName) {
             Objects.requireNonNull(packageName);
             mNetworkMappings.remove(packageName);
             return this;
@@ -160,7 +160,7 @@
 
     /** @hide */
     @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = {
-            OEM_NETWORK_PREFERENCE_DEFAULT,
+            OEM_NETWORK_PREFERENCE_UNINITIALIZED,
             OEM_NETWORK_PREFERENCE_OEM_PAID,
             OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK,
             OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY,
@@ -174,12 +174,14 @@
      *
      * @param value int value of OemNetworkPreference
      * @return string version of OemNetworkPreference
+     *
+     * @hide
      */
     @NonNull
     public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) {
         switch (value) {
-            case OEM_NETWORK_PREFERENCE_DEFAULT:
-                return "OEM_NETWORK_PREFERENCE_DEFAULT";
+            case OEM_NETWORK_PREFERENCE_UNINITIALIZED:
+                return "OEM_NETWORK_PREFERENCE_UNINITIALIZED";
             case OEM_NETWORK_PREFERENCE_OEM_PAID:
                 return "OEM_NETWORK_PREFERENCE_OEM_PAID";
             case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
similarity index 64%
rename from packages/Connectivity/framework/src/android/net/VpnManager.java
rename to core/java/android/net/VpnManager.java
index 1812509..f472ed4 100644
--- a/packages/Connectivity/framework/src/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -37,6 +38,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.GeneralSecurityException;
+import java.util.List;
 
 /**
  * This class provides an interface for apps to manage platform VPN profiles
@@ -55,18 +57,40 @@
 public class VpnManager {
     /** Type representing a lack of VPN @hide */
     public static final int TYPE_VPN_NONE = -1;
-    /** VPN service type code @hide */
+
+    /**
+     * A VPN created by an app using the {@link VpnService} API.
+     * @hide
+     */
     public static final int TYPE_VPN_SERVICE = 1;
-    /** Platform VPN type code @hide */
+
+    /**
+     * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}.
+     * @hide
+     */
     public static final int TYPE_VPN_PLATFORM = 2;
 
+    /**
+     * An IPsec VPN created by the built-in LegacyVpnRunner.
+     * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead.
+     * @hide
+     */
+    @Deprecated
+    public static final int TYPE_VPN_LEGACY = 3;
+
+    /**
+     * Channel for VPN notifications.
+     * @hide
+     */
+    public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
+
     /** @hide */
-    @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM})
+    @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY})
     @Retention(RetentionPolicy.SOURCE)
     public @interface VpnType {}
 
     @NonNull private final Context mContext;
-    @NonNull private final IConnectivityManager mService;
+    @NonNull private final IVpnManager mService;
 
     private static Intent getIntentForConfirmation() {
         final Intent intent = new Intent();
@@ -85,9 +109,9 @@
      *
      * @hide
      */
-    public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
+    public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) {
         mContext = checkNotNull(ctx, "missing Context");
-        mService = checkNotNull(service, "missing IConnectivityManager");
+        mService = checkNotNull(service, "missing IVpnManager");
     }
 
     /**
@@ -179,6 +203,19 @@
     }
 
     /**
+     * Resets all VPN settings back to factory defaults.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void factoryReset() {
+        try {
+            mService.factoryReset();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Prepare for a VPN application.
      * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
      * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
@@ -224,6 +261,108 @@
     }
 
     /**
+     * Checks if a VPN app supports always-on mode.
+     *
+     * In order to support the always-on feature, an app has to
+     * <ul>
+     *     <li>target {@link VERSION_CODES#N API 24} or above, and
+     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+     *         meta-data field.
+     * </ul>
+     *
+     * @param userId The identifier of the user for whom the VPN app is installed.
+     * @param vpnPackage The canonical package name of the VPN app.
+     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+     * @hide
+     */
+    public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+        try {
+            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param userId The identifier of the user to set an always-on VPN for.
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+     *        {@code false} otherwise.
+     * @param lockdownAllowlist The list of packages that are allowed to access network directly
+     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+     *         this method must be called when a package that should be allowed is installed or
+     *         uninstalled.
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
+            boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
+        try {
+            return mService.setAlwaysOnVpnPackage(
+                    userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the package name of the currently set always-on VPN application.
+     * If there is no always-on VPN set, or the VPN is provided by the system instead
+     * of by an app, {@code null} will be returned.
+     *
+     * @return Package name of VPN controller responsible for always-on VPN,
+     *         or {@code null} if none is set.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public String getAlwaysOnVpnPackageForUser(int userId) {
+        try {
+            return mService.getAlwaysOnVpnPackage(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return whether always-on VPN is in lockdown mode.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean isVpnLockdownEnabled(int userId) {
+        try {
+            return mService.isVpnLockdownEnabled(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return the list of packages that are allowed to access network when always-on VPN is in
+     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        try {
+            return mService.getVpnLockdownAllowlist(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the legacy VPN information for the specified user ID.
      * @hide
      */
diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java
similarity index 98%
rename from packages/Connectivity/framework/src/android/net/VpnService.java
rename to core/java/android/net/VpnService.java
index 8e90a119..e43b0b6 100644
--- a/packages/Connectivity/framework/src/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -170,12 +170,11 @@
             "android.net.VpnService.SUPPORTS_ALWAYS_ON";
 
     /**
-     * Use IConnectivityManager since those methods are hidden and not
-     * available in ConnectivityManager.
+     * Use IVpnManager since those methods are hidden and not available in VpnManager.
      */
-    private static IConnectivityManager getService() {
-        return IConnectivityManager.Stub.asInterface(
-                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+    private static IVpnManager getService() {
+        return IVpnManager.Stub.asInterface(
+                ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE));
     }
 
     /**
@@ -226,15 +225,15 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.CONTROL_VPN)
     public static void prepareAndAuthorize(Context context) {
-        IConnectivityManager cm = getService();
+        IVpnManager vm = getService();
         String packageName = context.getPackageName();
         try {
             // Only prepare if we're not already prepared.
             int userId = context.getUserId();
-            if (!cm.prepareVpn(packageName, null, userId)) {
-                cm.prepareVpn(null, packageName, userId);
+            if (!vm.prepareVpn(packageName, null, userId)) {
+                vm.prepareVpn(null, packageName, userId);
             }
-            cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
+            vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE);
         } catch (RemoteException e) {
             // ignore
         }
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index fa090f5..1a38338 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -28,8 +28,10 @@
 import android.os.ServiceSpecificException;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -67,8 +69,7 @@
 public class VcnManager {
     @NonNull private static final String TAG = VcnManager.class.getSimpleName();
 
-    @VisibleForTesting
-    public static final Map<
+    private static final Map<
                     VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
             REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>();
 
@@ -88,6 +89,18 @@
         mService = requireNonNull(service, "missing service");
     }
 
+    /**
+     * Get all currently registered VcnUnderlyingNetworkPolicyListeners for testing purposes.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    public static Map<VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+            getAllPolicyListeners() {
+        return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS);
+    }
+
     // TODO: Make setVcnConfig(), clearVcnConfig() Public API
     /**
      * Sets the VCN configuration for a given subscription group.
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index f2b466d..bf229e0 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -47,6 +47,9 @@
             POWER_COMPONENT_SYSTEM_SERVICES,
             POWER_COMPONENT_SENSORS,
             POWER_COMPONENT_GNSS,
+            POWER_COMPONENT_WAKELOCK,
+            POWER_COMPONENT_SCREEN,
+            POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface PowerComponent {
@@ -63,8 +66,14 @@
     public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
     public static final int POWER_COMPONENT_SENSORS = 9;
     public static final int POWER_COMPONENT_GNSS = 10;
+    public static final int POWER_COMPONENT_WAKELOCK = 12;
+    public static final int POWER_COMPONENT_SCREEN = 13;
+    // 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_COUNT = 11;
+    public static final int POWER_COMPONENT_COUNT = 15;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -85,6 +94,8 @@
             TIME_COMPONENT_MOBILE_RADIO,
             TIME_COMPONENT_SENSORS,
             TIME_COMPONENT_GNSS,
+            TIME_COMPONENT_WAKELOCK,
+            TIME_COMPONENT_SCREEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface TimeComponent {
@@ -101,8 +112,10 @@
     public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
     public static final int TIME_COMPONENT_SENSORS = 9;
     public static final int TIME_COMPONENT_GNSS = 10;
+    public static final int TIME_COMPONENT_WAKELOCK = 12;
+    public static final int TIME_COMPONENT_SCREEN = 13;
 
-    public static final int TIME_COMPONENT_COUNT = 11;
+    public static final int TIME_COMPONENT_COUNT = 14;
 
     public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
@@ -232,5 +245,13 @@
                     componentUsageTimeMillis);
             return (T) this;
         }
+
+        /**
+         * Returns the total power accumulated by this builder so far. It may change
+         * by the time the {@code build()} method is called.
+         */
+        public double getTotalPower() {
+            return mPowerComponentsBuilder.getTotalPower();
+        }
     }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc86a60..01a8901 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -994,6 +994,19 @@
          */
         public abstract long getScreenOnEnergy();
 
+        /**
+         * Returns the energies used by this uid for each
+         * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+         * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+         *
+         * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+         *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+         *         supported.
+         *
+         * {@hide}
+         */
+        public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
         public static abstract class Sensor {
 
             @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2511,6 +2524,19 @@
      */
     public abstract long getScreenDozeEnergy();
 
+    /**
+     * Returns the energies used for each
+     * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
+     * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+     *
+     * @return energies (in microjoules) used since boot for each (custom) energy consumer of
+     *         type OTHER, indexed by their ordinal. Returns null if no energy reporting is
+     *         supported.
+     *
+     * {@hide}
+     */
+    public abstract @Nullable long[] getCustomMeasuredEnergiesMicroJoules();
+
     public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
         new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
@@ -5268,6 +5294,15 @@
                         pw.print(" flash=");
                         printmAh(pw, bs.flashlightPowerMah);
                     }
+                    if (bs.customMeasuredPowerMah != null) {
+                        for (int idx = 0; idx < bs.customMeasuredPowerMah.length; idx++) {
+                            final double customPowerMah = bs.customMeasuredPowerMah[idx];
+                            if (customPowerMah != 0) {
+                                pw.print(" custom[" + idx + "]=");
+                                printmAh(pw, customPowerMah);
+                            }
+                        }
+                    }
                     pw.print(" )");
                 }
 
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index af8e8de..305815f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -33,20 +33,33 @@
     private final int mDischargePercentage;
     private final ArrayList<UidBatteryConsumer> mUidBatteryConsumers;
     private final ArrayList<SystemBatteryConsumer> mSystemBatteryConsumers;
+    private final ArrayList<UserBatteryConsumer> mUserBatteryConsumers;
 
     private BatteryUsageStats(@NonNull Builder builder) {
         mConsumedPower = builder.mConsumedPower;
         mDischargePercentage = builder.mDischargePercentage;
+
         int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
         mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
         for (int i = 0; i < uidBatteryConsumerCount; i++) {
-            mUidBatteryConsumers.add(builder.mUidBatteryConsumerBuilders.valueAt(i).build());
+            UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
+                    builder.mUidBatteryConsumerBuilders.valueAt(i);
+            if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
+                mUidBatteryConsumers.add(uidBatteryConsumerBuilder.build());
+            }
         }
+
         int systemBatteryConsumerCount = builder.mSystemBatteryConsumerBuilders.size();
         mSystemBatteryConsumers = new ArrayList<>(systemBatteryConsumerCount);
         for (int i = 0; i < systemBatteryConsumerCount; i++) {
             mSystemBatteryConsumers.add(builder.mSystemBatteryConsumerBuilders.valueAt(i).build());
         }
+
+        int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
+        mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
+        for (int i = 0; i < userBatteryConsumerCount; i++) {
+            mUserBatteryConsumers.add(builder.mUserBatteryConsumerBuilders.valueAt(i).build());
+        }
     }
 
     /**
@@ -75,6 +88,11 @@
         return mSystemBatteryConsumers;
     }
 
+    @NonNull
+    public List<UserBatteryConsumer> getUserBatteryConsumers() {
+        return mUserBatteryConsumers;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -85,6 +103,8 @@
         source.readParcelableList(mUidBatteryConsumers, getClass().getClassLoader());
         mSystemBatteryConsumers = new ArrayList<>();
         source.readParcelableList(mSystemBatteryConsumers, getClass().getClassLoader());
+        mUserBatteryConsumers = new ArrayList<>();
+        source.readParcelableList(mUserBatteryConsumers, getClass().getClassLoader());
         mConsumedPower = source.readDouble();
         mDischargePercentage = source.readInt();
     }
@@ -93,6 +113,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mUidBatteryConsumers, flags);
         dest.writeParcelableList(mSystemBatteryConsumers, flags);
+        dest.writeParcelableList(mUserBatteryConsumers, flags);
         dest.writeDouble(mConsumedPower);
         dest.writeInt(mDischargePercentage);
     }
@@ -120,6 +141,8 @@
                 new SparseArray<>();
         private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
                 new SparseArray<>();
+        private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
+                new SparseArray<>();
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount) {
             mCustomPowerComponentCount = customPowerComponentCount;
@@ -137,7 +160,6 @@
         /**
          * Sets the battery discharge amount since BatteryStats reset as percentage of the full
          * charge.
-         *
          */
         @SuppressLint("PercentageInt") // See b/174188159
         @NonNull
@@ -173,8 +195,8 @@
         }
 
         /**
-         * Creates or returns a exiting UidBatteryConsumer, which represents battery attribution
-         * data for an individual UID.
+         * Creates or returns a exiting SystemBatteryConsumer, which represents battery attribution
+         * data for a specific drain type.
          */
         @NonNull
         public SystemBatteryConsumer.Builder getOrCreateSystemBatteryConsumerBuilder(
@@ -188,6 +210,21 @@
             return builder;
         }
 
+        /**
+         * Creates or returns a exiting UserBatteryConsumer, which represents battery attribution
+         * data for an individual {@link UserHandle}.
+         */
+        @NonNull
+        public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) {
+            UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId);
+            if (builder == null) {
+                builder = new UserBatteryConsumer.Builder(mCustomPowerComponentCount,
+                        mCustomTimeComponentCount, userId);
+                mUserBatteryConsumerBuilders.put(userId, builder);
+            }
+            return builder;
+        }
+
         @NonNull
         public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() {
             return mUidBatteryConsumerBuilders;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 48e7389..5b5fe1d 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.util.IntArray;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,9 +54,13 @@
     public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
 
     private final int mFlags;
+    @NonNull
+    private final int[] mUserIds;
 
     private BatteryUsageStatsQuery(@NonNull Builder builder) {
         mFlags = builder.mFlags;
+        mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
+                : new int[]{UserHandle.USER_ALL};
     }
 
     @BatteryUsageStatsFlags
@@ -63,13 +68,28 @@
         return mFlags;
     }
 
+    /**
+     * Returns an array of users for which the attribution is requested.  It may
+     * contain {@link UserHandle#USER_ALL} to indicate that the attribution
+     * should be performed for all users. Battery consumed by users <b>not</b> included
+     * in this array will be returned in the aggregated form as {@link UserBatteryConsumer}'s.
+     */
+    @NonNull
+    public int[] getUserIds() {
+        return mUserIds;
+    }
+
     private BatteryUsageStatsQuery(Parcel in) {
         mFlags = in.readInt();
+        mUserIds = new int[in.readInt()];
+        in.readIntArray(mUserIds);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mFlags);
+        dest.writeInt(mUserIds.length);
+        dest.writeIntArray(mUserIds);
     }
 
     @Override
@@ -96,6 +116,7 @@
      */
     public static final class Builder {
         private int mFlags;
+        private IntArray mUserIds;
 
         /**
          * Builds a read-only BatteryUsageStatsQuery object.
@@ -105,6 +126,18 @@
         }
 
         /**
+         * Add a user whose battery stats should be included in the battery usage stats.
+         * {@link UserHandle#USER_ALL} will be used by default if no users are added explicitly.
+         */
+        public Builder addUser(@NonNull UserHandle userHandle) {
+            if (mUserIds == null) {
+                mUserIds = new IntArray(1);
+            }
+            mUserIds.add(userHandle.getIdentifier());
+            return this;
+        }
+
+        /**
          * Sets flags to modify the behavior of {@link BatteryStatsManager#getBatteryUsageStats}.
          */
         public Builder setFlags(@BatteryUsageStatsFlags int flags) {
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index cb4e9cb..c8e682c 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -86,7 +87,20 @@
         return 0;
     }
 
-    /** @hide */
+    /**
+     * Gets the estimated duration of the combined vibration in milliseconds.
+     *
+     * <p>For synced combinations this means the maximum duration of any individual {@link
+     * VibrationEffect}. For sequential combinations, this is a sum of each step and delays.
+     *
+     * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative
+     * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g.
+     * Prebaked effects where the length is device and potentially run-time dependent), this returns
+     * -1.
+     *
+     * @hide
+     */
+    @TestApi
     public abstract long getDuration();
 
     /** @hide */
@@ -256,6 +270,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Mono extends CombinedVibrationEffect {
         private final VibrationEffect mEffect;
 
@@ -267,6 +282,7 @@
             mEffect = effect;
         }
 
+        @NonNull
         public VibrationEffect getEffect() {
             return mEffect;
         }
@@ -282,6 +298,7 @@
             mEffect.validate();
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return true;
@@ -307,7 +324,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_MONO);
             mEffect.writeToParcel(out, flags);
         }
@@ -335,6 +357,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Stereo extends CombinedVibrationEffect {
 
         /** Mapping vibrator ids to effects. */
@@ -357,6 +380,7 @@
         }
 
         /** Effects to be performed in sync, where each key represents the vibrator id. */
+        @NonNull
         public SparseArray<VibrationEffect> getEffects() {
             return mEffects;
         }
@@ -394,6 +418,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             return mEffects.indexOfKey(vibratorId) >= 0;
@@ -418,7 +443,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return mEffects.contentHashCode();
         }
 
         @Override
@@ -427,7 +452,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_STEREO);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
@@ -459,6 +489,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final class Sequential extends CombinedVibrationEffect {
         private final List<CombinedVibrationEffect> mEffects;
         private final List<Integer> mDelays;
@@ -480,11 +511,13 @@
         }
 
         /** Effects to be performed in sequence. */
+        @NonNull
         public List<CombinedVibrationEffect> getEffects() {
             return mEffects;
         }
 
         /** Delay to be applied before each effect in {@link #getEffects()}. */
+        @NonNull
         public List<Integer> getDelays() {
             return mDelays;
         }
@@ -542,6 +575,7 @@
             }
         }
 
+        /** @hide */
         @Override
         public boolean hasVibrator(int vibratorId) {
             final int effectCount = mEffects.size();
@@ -564,7 +598,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mEffects);
+            return Objects.hash(mEffects, mDelays);
         }
 
         @Override
@@ -573,7 +607,12 @@
         }
 
         @Override
-        public void writeToParcel(Parcel out, int flags) {
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
             out.writeInt(mEffects.size());
             for (int i = 0; i < mEffects.size(); i++) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 9584bc7..e3b13f4 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1902,7 +1902,8 @@
      * Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long
      * array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
      * Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
-     * another array to also retrieve the separate memtrack size.
+     * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the
+     * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other.
      *
      * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
      * @hide
@@ -2557,6 +2558,22 @@
     public static native long getZramFreeKb();
 
     /**
+     * Return total memory size in kilobytes for exported DMA-BUFs or -1 if
+     * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read.
+     *
+     * @hide
+     */
+    public static native long getDmabufTotalExportedKb();
+
+    /**
+     * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF
+     * heaps frameworks or -1 in the case of an error.
+     *
+     * @hide
+     */
+    public static native long getDmabufHeapTotalExportedKb();
+
+    /**
      * Return memory size in kilobytes allocated for ION heaps or -1 if
      * /sys/kernel/ion/total_heaps_kb could not be read.
      *
@@ -2565,6 +2582,14 @@
     public static native long getIonHeapsSizeKb();
 
     /**
+     * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if
+     * /sys/kernel/dma_heap/total_pools_kb could not be read.
+     *
+     * @hide
+     */
+    public static native long getDmabufHeapPoolsSizeKb();
+
+    /**
      * Return memory size in kilobytes allocated for ION pools or -1 if
      * /sys/kernel/ion/total_pools_kb could not be read.
      *
@@ -2573,13 +2598,20 @@
     public static native long getIonPoolsSizeKb();
 
     /**
-     * Return ION memory mapped by processes in kB.
+     * Return GPU DMA buffer usage in kB or -1 on error.
+     *
+     * @hide
+     */
+    public static native long getGpuDmaBufUsageKb();
+
+    /**
+     * Return DMA-BUF memory mapped by processes in kB.
      * Notes:
      *  * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
      *
      * @hide
      */
-    public static native long getIonMappedSizeKb();
+    public static native long getDmabufMappedSizeKb();
 
     /**
      * Return memory size in kilobytes used by GPU.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 518e29d..124c0b0 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.AppGlobals;
@@ -836,6 +837,21 @@
     public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";
 
     /**
+     * Standard directory in which to place any audio files which are
+     * recordings.
+     */
+    @NonNull
+    // The better way is that expose a static method getRecordingDirectories.
+    // But since it's an existing API surface and developers already
+    // used to DIRECTORY_* constants, we should keep using this pattern
+    // for consistency. We use SuppressLint here to avoid exposing a final
+    // field. A final field will prevent us from ever changing the value of
+    // DIRECTORY_RECORDINGS. Not that it's likely that we will ever need to
+    // change it, but it's better to have such option.
+    @SuppressLint({"MutableBareField", "AllUpper"})
+    public static String DIRECTORY_RECORDINGS = "Recordings";
+
+    /**
      * List of standard storage directories.
      * <p>
      * Each of its values have its own constant:
@@ -851,6 +867,7 @@
      *   <li>{@link #DIRECTORY_DCIM}
      *   <li>{@link #DIRECTORY_DOCUMENTS}
      *   <li>{@link #DIRECTORY_AUDIOBOOKS}
+     *   <li>{@link #DIRECTORY_RECORDINGS}
      * </ul>
      * @hide
      */
@@ -866,6 +883,7 @@
             DIRECTORY_DCIM,
             DIRECTORY_DOCUMENTS,
             DIRECTORY_AUDIOBOOKS,
+            DIRECTORY_RECORDINGS,
     };
 
     /**
@@ -891,6 +909,7 @@
     /** {@hide} */ public static final int HAS_DCIM = 1 << 8;
     /** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9;
     /** {@hide} */ public static final int HAS_AUDIOBOOKS = 1 << 10;
+    /** {@hide} */ public static final int HAS_RECORDINGS = 1 << 11;
 
     /** {@hide} */ public static final int HAS_ANDROID = 1 << 16;
     /** {@hide} */ public static final int HAS_OTHER = 1 << 17;
@@ -921,6 +940,7 @@
                 else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM;
                 else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS;
                 else if (DIRECTORY_AUDIOBOOKS.equals(name)) res |= HAS_AUDIOBOOKS;
+                else if (DIRECTORY_RECORDINGS.equals(name)) res |= HAS_RECORDINGS;
                 else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
                 else res |= HAS_OTHER;
             }
@@ -1339,8 +1359,17 @@
         }
 
         final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
-        return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
-                uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
+        final String opPackageName = context.getOpPackageName();
+
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED) {
+            return true;
+        }
+
+        // Legacy external storage access is granted to instrumentations invoked with
+        // "--no-isolated-storage" flag.
+        return appOps.noteOpNoThrow(AppOpsManager.OP_NO_ISOLATED_STORAGE, uid,
+                opPackageName) == AppOpsManager.MODE_ALLOWED;
     }
 
     private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 52f0ce1..4d160da 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -35,4 +35,9 @@
      * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries
      */
     Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries();
+
+    /**
+     * @see SystemConfigManager#getSystemPermissionUids
+     */
+    int[] getSystemPermissionUids(String permissionName);
 }
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
deleted file mode 100644
index 1cd48dc..0000000
--- a/core/java/android/os/IVibratorService.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007, 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.os;
-
-import android.os.VibrationEffect;
-import android.os.VibrationAttributes;
-import android.os.VibratorInfo;
-import android.os.IVibratorStateListener;
-
-/** {@hide} */
-interface IVibratorService
-{
-    boolean hasVibrator();
-    boolean isVibrating();
-    VibratorInfo getVibratorInfo();
-    boolean registerVibratorStateListener(in IVibratorStateListener listener);
-    boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
-    boolean hasAmplitudeControl();
-    void vibrate(int uid, String opPkg, in VibrationEffect effect,
-            in VibrationAttributes attributes, String reason, IBinder token);
-    void cancelVibrate(IBinder token);
-}
-
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 2559a33..a04047d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,15 +1,18 @@
 # Haptics
+per-file CombinedVibrationEffect.aidl = michaelwr@google.com
+per-file CombinedVibrationEffect.java = michaelwr@google.com
 per-file ExternalVibration.aidl = michaelwr@google.com
 per-file ExternalVibration.java = michaelwr@google.com
 per-file IExternalVibrationController.aidl = michaelwr@google.com
 per-file IExternalVibratorService.aidl = michaelwr@google.com
 per-file IVibratorManagerService.aidl = michaelwr@google.com
-per-file IVibratorService.aidl = michaelwr@google.com
 per-file NullVibrator.java = michaelwr@google.com
 per-file SystemVibrator.java = michaelwr@google.com
+per-file SystemVibratorManager.java = michaelwr@google.com
 per-file VibrationEffect.aidl = michaelwr@google.com
 per-file VibrationEffect.java = michaelwr@google.com
 per-file Vibrator.java = michaelwr@google.com
+per-file VibratorManager.java = michaelwr@google.com
 
 # PowerManager
 per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 1337d55..ac23285 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -38,11 +38,7 @@
         mCustomPowerComponentCount = builder.mCustomPowerComponentCount;
         mPowerComponents = builder.mPowerComponents;
         mTimeComponents = builder.mTimeComponents;
-        double totalPower = 0;
-        for (int i = mPowerComponents.length - 1; i >= 0; i--) {
-            totalPower += mPowerComponents[i];
-        }
-        mTotalPowerConsumed = totalPower;
+        mTotalPowerConsumed = builder.getTotalPower();
     }
 
     PowerComponents(@NonNull Parcel source) {
@@ -264,6 +260,18 @@
         }
 
         /**
+         * Returns the total power accumulated by this builder so far. It may change
+         * by the time the {@code build()} method is called.
+         */
+        public double getTotalPower() {
+            double totalPower = 0;
+            for (int i = mPowerComponents.length - 1; i >= 0; i--) {
+                totalPower += mPowerComponents[i];
+            }
+            return totalPower;
+        }
+
+        /**
          * Creates a read-only object out of the Builder values.
          */
         @NonNull
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 059e932..3774fb5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1898,7 +1898,8 @@
      * These estimates will be displayed on system UI surfaces in place of the system computed
      * value.
      *
-     * Calling this requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+     * Calling this requires either the {@link android.Manifest.permission#DEVICE_POWER} or the
+     * {@link android.Manifest.permission#BATTERY_PREDICTION} permissions.
      *
      * @param timeRemaining  The time remaining as a {@link Duration}.
      * @param isPersonalized true if personalized based on device usage history, false otherwise.
@@ -1906,7 +1907,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.BATTERY_PREDICTION,
+            android.Manifest.permission.DEVICE_POWER
+    })
     public void setBatteryDischargePrediction(@NonNull Duration timeRemaining,
             boolean isPersonalized) {
         if (timeRemaining == null) {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 8068c87..54d2df8 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -970,6 +970,16 @@
             throws IllegalArgumentException, SecurityException;
 
     /**
+     *
+     * Create a new process group in the cgroup uid/pid hierarchy
+     *
+     * @return <0 in case of error
+     *
+     * @hide
+     */
+    public static final native int createProcessGroup(int uid, int pid);
+
+    /**
      * On some devices, the foreground process may have one or more CPU
      * cores exclusively reserved for it. This method can be used to
      * retrieve which cores that are (if any), so the calling process
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 3f0632b..9bfa8ad 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -111,4 +111,22 @@
             return Collections.emptyMap();
         }
     }
+
+    /**
+     * Get uids which have been granted given permission in system configuration.
+     *
+     * The uids and assigning permissions are defined on data/etc/platform.xml
+     *
+     * @param permissionName The target permission.
+     * @return The uids have been granted given permission in system configuration.
+     */
+    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    @NonNull
+    public int[] getSystemPermissionUids(@NonNull String permissionName) {
+        try {
+            return mInterface.getSystemPermissionUids(permissionName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 30afe38..b42a495 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -17,19 +17,18 @@
 package android.os;
 
 import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -41,142 +40,46 @@
 public class SystemVibrator extends Vibrator {
     private static final String TAG = "Vibrator";
 
-    private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
-    private static final int VIBRATOR_PRESENT_YES = 1;
-    private static final int VIBRATOR_PRESENT_NO = 2;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
-    private @interface VibratorPresent {}
-
-    private final IVibratorService mService;
-    private final IVibratorManagerService mManagerService;
-    private final Object mLock = new Object();
-    private final Binder mToken = new Binder();
+    private final VibratorManager mVibratorManager;
     private final Context mContext;
-    @GuardedBy("mLock")
-    private VibratorInfo mVibratorInfo;
-    @GuardedBy("mLock")
-    @VibratorPresent
-    private int mVibratorPresent;
 
-    @GuardedBy("mDelegates")
-    private final ArrayMap<OnVibratorStateChangedListener,
-            OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
+    @GuardedBy("mBrokenListeners")
+    private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
 
-    @UnsupportedAppUsage
-    public SystemVibrator() {
-        mContext = null;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
-    }
+    @GuardedBy("mRegisteredListeners")
+    private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
+            mRegisteredListeners = new ArrayMap<>();
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
         super(context);
         mContext = context;
-        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
-        mManagerService = IVibratorManagerService.Stub.asInterface(
-                ServiceManager.getService("vibrator_manager"));
+        mVibratorManager = mContext.getSystemService(VibratorManager.class);
     }
 
     @Override
     public boolean hasVibrator() {
-        try {
-            synchronized (mLock) {
-                if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
-                    mVibratorPresent =
-                            mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
-                }
-                return mVibratorPresent == VIBRATOR_PRESENT_YES;
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator presence", e);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
             return false;
         }
+        return mVibratorManager.getVibratorIds().length > 0;
     }
 
-    /**
-     * Check whether the vibrator is vibrating.
-     *
-     * @return True if the hardware is vibrating, otherwise false.
-     */
     @Override
     public boolean isVibrating() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.isVibrating();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                return true;
+            }
         }
         return false;
     }
 
-    private class OnVibratorStateChangedListenerDelegate extends
-            IVibratorStateListener.Stub {
-        private final Executor mExecutor;
-        private final OnVibratorStateChangedListener mListener;
-
-        OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
-                @NonNull Executor executor) {
-            mExecutor = executor;
-            mListener = listener;
-        }
-
-        @Override
-        public void onVibrating(boolean isVibrating) {
-            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state change. If the listener was previously added and not
-     * removed, this call will be ignored.
-     *
-     * @param listener Listener to be added.
-     * @param executor The {@link Executor} on which the listener's callbacks will be executed on.
-     */
-    @Override
-    public void addVibratorStateListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnVibratorStateChangedListener listener) {
-        Objects.requireNonNull(listener);
-        Objects.requireNonNull(executor);
-        if (mService == null) {
-            Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
-            return;
-        }
-
-        synchronized (mDelegates) {
-            // If listener is already registered, reject and return.
-            if (mDelegates.containsKey(listener)) {
-                Log.w(TAG, "Listener already registered.");
-                return;
-            }
-            try {
-                final OnVibratorStateChangedListenerDelegate delegate =
-                        new OnVibratorStateChangedListenerDelegate(listener, executor);
-                if (!mService.registerVibratorStateListener(delegate)) {
-                    Log.w(TAG, "Failed to register vibrate state listener");
-                    return;
-                }
-                mDelegates.put(listener, delegate);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
-     * If the listener was previously added and not removed, this call will be ignored.
-     *
-     * @param listener listener to be added
-     */
     @Override
     public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
@@ -187,88 +90,128 @@
         addVibratorStateListener(mContext.getMainExecutor(), listener);
     }
 
-    /**
-     * Removes the listener for vibrator state changes. If the listener was not previously
-     * registered, this call will do nothing.
-     *
-     * @param listener Listener to be removed.
-     */
     @Override
-    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+    public void addVibratorStateListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnVibratorStateChangedListener listener) {
         Objects.requireNonNull(listener);
-        if (mService == null) {
-            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
+        Objects.requireNonNull(executor);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
             return;
         }
-        synchronized (mDelegates) {
-            // Check if the listener is registered, otherwise will return.
-            if (mDelegates.containsKey(listener)) {
-                final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
-                try {
-                    if (!mService.unregisterVibratorStateListener(delegate)) {
-                        Log.w(TAG, "Failed to unregister vibrate state listener");
-                        return;
-                    }
-                    mDelegates.remove(listener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+        AllVibratorsStateListener delegate = null;
+        try {
+            synchronized (mRegisteredListeners) {
+                // If listener is already registered, reject and return.
+                if (mRegisteredListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                delegate = new AllVibratorsStateListener(executor, listener);
+                delegate.register(mVibratorManager);
+                mRegisteredListeners.put(listener, delegate);
+                delegate = null;
+            }
+        } finally {
+            if (delegate != null && delegate.hasRegisteredListeners()) {
+                // The delegate listener was left in a partial state with listeners registered to
+                // some but not all vibrators. Keep track of this to try to unregister them later.
+                synchronized (mBrokenListeners) {
+                    mBrokenListeners.add(delegate);
                 }
             }
+            tryUnregisterBrokenListeners();
         }
     }
 
     @Override
+    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+        Objects.requireNonNull(listener);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
+            return;
+        }
+        synchronized (mRegisteredListeners) {
+            if (mRegisteredListeners.containsKey(listener)) {
+                AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
+                delegate.unregister(mVibratorManager);
+                mRegisteredListeners.remove(listener);
+            }
+        }
+        tryUnregisterBrokenListeners();
+    }
+
+    @Override
     public boolean hasAmplitudeControl() {
-        if (mService == null) {
-            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
             return false;
         }
-        try {
-            return mService.hasAmplitudeControl();
-        } catch (RemoteException e) {
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            return false;
         }
-        return false;
+        for (int vibratorId : vibratorIds) {
+            if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
     public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
             AudioAttributes attributes) {
-        if (mManagerService == null) {
-            Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
             return false;
         }
-        try {
-            VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
-            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
-            return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to set always-on effect.", e);
-        }
-        return false;
+        VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
     }
 
     @Override
     public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
             String reason, @NonNull VibrationAttributes attributes) {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to vibrate.", e);
-        }
+        CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+        mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
     }
 
     @Override
     public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         int[] supported = new int[effectIds.length];
-        for (int i = 0; i < effectIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
-                    : vibratorInfo.isEffectSupported(effectIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
+            return supported;
+        }
+        int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).areEffectsSupported(effectIds);
+        }
+        Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
+        for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
+                if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
+                    break;
+                } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
+                    supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
+                }
+            }
         }
         return supported;
     }
@@ -276,42 +219,169 @@
     @Override
     public boolean[] arePrimitivesSupported(
             @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
-        VibratorInfo vibratorInfo = getVibratorInfo();
         boolean[] supported = new boolean[primitiveIds.length];
-        for (int i = 0; i < primitiveIds.length; i++) {
-            supported[i] = vibratorInfo == null
-                    ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        if (vibratorIds.length == 0) {
+            Arrays.fill(supported, false);
+            return supported;
+        }
+        boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
+        for (int i = 0; i < vibratorIds.length; i++) {
+            vibratorSupportMap[i] = mVibratorManager.getVibrator(
+                    vibratorIds[i]).arePrimitivesSupported(primitiveIds);
+        }
+        Arrays.fill(supported, true);
+        for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
+            for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
+                if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
+                    supported[primitiveIdx] = false;
+                    break;
+                }
+            }
         }
         return supported;
     }
 
     @Override
     public void cancel() {
-        if (mService == null) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
             return;
         }
-        try {
-            mService.cancelVibrate(mToken);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to cancel vibration.", e);
+        mVibratorManager.cancel();
+    }
+
+    /**
+     * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
+     * that were left registered to vibrators after failures to register them to all vibrators.
+     *
+     * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
+     * and also fails to unregister any previously registered single listeners to other vibrators.
+     *
+     * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
+     * fail silently and attempt to unregister the same broken listener later.
+     */
+    private void tryUnregisterBrokenListeners() {
+        synchronized (mBrokenListeners) {
+            try {
+                for (int i = mBrokenListeners.size(); --i >= 0; ) {
+                    mBrokenListeners.get(i).unregister(mVibratorManager);
+                    mBrokenListeners.remove(i);
+                }
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Failed to unregister broken listener", e);
+            }
         }
     }
 
-    @Nullable
-    private VibratorInfo getVibratorInfo() {
-        try {
+    /** Listener for a single vibrator state change. */
+    private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
+        private final AllVibratorsStateListener mAllVibratorsListener;
+        private final int mVibratorIdx;
+
+        SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
+            mAllVibratorsListener = listener;
+            mVibratorIdx = vibratorIdx;
+        }
+
+        @Override
+        public void onVibratorStateChanged(boolean isVibrating) {
+            mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
+        }
+    }
+
+    /** Listener for all vibrators state change. */
+    private static class AllVibratorsStateListener {
+        private final Object mLock = new Object();
+        private final Executor mExecutor;
+        private final OnVibratorStateChangedListener mDelegate;
+
+        @GuardedBy("mLock")
+        private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
+                new SparseArray<>();
+
+        @GuardedBy("mLock")
+        private int mInitializedMask;
+        @GuardedBy("mLock")
+        private int mVibratingMask;
+
+        AllVibratorsStateListener(@NonNull Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            mExecutor = executor;
+            mDelegate = listener;
+        }
+
+        boolean hasRegisteredListeners() {
             synchronized (mLock) {
-                if (mVibratorInfo != null) {
-                    return mVibratorInfo;
-                }
-                if (mService == null) {
-                    return null;
-                }
-                return mVibratorInfo = mService.getVibratorInfo();
+                return mVibratorListeners.size() > 0;
             }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to query vibrator info");
-            throw e.rethrowFromSystemServer();
+        }
+
+        void register(VibratorManager vibratorManager) {
+            int[] vibratorIds = vibratorManager.getVibratorIds();
+            synchronized (mLock) {
+                for (int i = 0; i < vibratorIds.length; i++) {
+                    int vibratorId = vibratorIds[i];
+                    SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
+                    try {
+                        vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
+                                listener);
+                        mVibratorListeners.put(vibratorId, listener);
+                    } catch (RuntimeException e) {
+                        try {
+                            unregister(vibratorManager);
+                        } catch (RuntimeException e1) {
+                            Log.w(TAG,
+                                    "Failed to unregister listener while recovering from a failed "
+                                            + "register call", e1);
+                        }
+                        throw e;
+                    }
+                }
+            }
+        }
+
+        void unregister(VibratorManager vibratorManager) {
+            synchronized (mLock) {
+                for (int i = mVibratorListeners.size(); --i >= 0; ) {
+                    int vibratorId = mVibratorListeners.keyAt(i);
+                    SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
+                    vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
+                    mVibratorListeners.removeAt(i);
+                }
+            }
+        }
+
+        void onVibrating(int vibratorIdx, boolean vibrating) {
+            mExecutor.execute(() -> {
+                boolean anyVibrating;
+                synchronized (mLock) {
+                    int allInitializedMask = 1 << mVibratorListeners.size() - 1;
+                    int vibratorMask = 1 << vibratorIdx;
+                    if ((mInitializedMask & vibratorMask) == 0) {
+                        // First state report for this vibrator, set vibrating initial value.
+                        mInitializedMask |= vibratorMask;
+                        mVibratingMask |= vibrating ? vibratorMask : 0;
+                    } else {
+                        // Flip vibrating value, if changed.
+                        boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
+                        if (prevVibrating != vibrating) {
+                            mVibratingMask ^= vibratorMask;
+                        }
+                    }
+                    if (mInitializedMask != allInitializedMask) {
+                        // Wait for all vibrators initial state to be reported before delegating.
+                        return;
+                    }
+                    anyVibrating = mVibratingMask != 0;
+                }
+                mDelegate.onVibratorStateChanged(anyVibrating);
+            });
         }
     }
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
new file mode 100644
index 0000000..b528eb1
--- /dev/null
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -0,0 +1,360 @@
+/*
+ * 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.os;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * VibratorManager implementation that controls the system vibrators.
+ *
+ * @hide
+ */
+public class SystemVibratorManager extends VibratorManager {
+    private static final String TAG = "VibratorManager";
+
+    private final IVibratorManagerService mService;
+    private final Context mContext;
+    private final Binder mToken = new Binder();
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int[] mVibratorIds;
+    @GuardedBy("mLock")
+    private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
+            OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    public SystemVibratorManager(Context context) {
+        super(context);
+        mContext = context;
+        mService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
+    }
+
+    @NonNull
+    @Override
+    public int[] getVibratorIds() {
+        synchronized (mLock) {
+            if (mVibratorIds != null) {
+                return mVibratorIds;
+            }
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
+                } else {
+                    return mVibratorIds = mService.getVibratorIds();
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return new int[0];
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getVibrator(int vibratorId) {
+        synchronized (mLock) {
+            Vibrator vibrator = mVibrators.get(vibratorId);
+            if (vibrator != null) {
+                return vibrator;
+            }
+            VibratorInfo info = null;
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
+                } else {
+                    info = mService.getVibratorInfo(vibratorId);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            if (info != null) {
+                vibrator = new SingleVibrator(info);
+                mVibrators.put(vibratorId, vibrator);
+            } else {
+                vibrator = NullVibrator.getInstance();
+            }
+            return vibrator;
+        }
+    }
+
+    @NonNull
+    @Override
+    public Vibrator getDefaultVibrator() {
+        return mContext.getSystemService(Vibrator.class);
+    }
+
+    @Override
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
+            return false;
+        }
+        try {
+            return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to set always-on effect.", e);
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to vibrate.", e);
+        }
+    }
+
+    @Override
+    public void cancel() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.cancelVibrate(mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel vibration.", e);
+        }
+    }
+
+    /** Listener for vibrations on a single vibrator. */
+    private static class OnVibratorStateChangedListenerDelegate extends
+            IVibratorStateListener.Stub {
+        private final Executor mExecutor;
+        private final Vibrator.OnVibratorStateChangedListener mListener;
+
+        OnVibratorStateChangedListenerDelegate(
+                @NonNull Vibrator.OnVibratorStateChangedListener listener,
+                @NonNull Executor executor) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onVibrating(boolean isVibrating) {
+            mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
+        }
+    }
+
+    /** Controls vibrations on a single vibrator. */
+    private final class SingleVibrator extends Vibrator {
+        private final VibratorInfo mVibratorInfo;
+
+        SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
+            mVibratorInfo = vibratorInfo;
+        }
+
+        @Override
+        public int getId() {
+            return mVibratorInfo.getId();
+        }
+
+        @Override
+        public boolean hasVibrator() {
+            return true;
+        }
+
+        @Override
+        public boolean hasAmplitudeControl() {
+            return mVibratorInfo.hasAmplitudeControl();
+        }
+
+        @NonNull
+        @Override
+        public int[] areEffectsSupported(@NonNull int... effectIds) {
+            int[] supported = new int[effectIds.length];
+            for (int i = 0; i < effectIds.length; i++) {
+                supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean[] arePrimitivesSupported(
+                @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
+            boolean[] supported = new boolean[primitiveIds.length];
+            for (int i = 0; i < primitiveIds.length; i++) {
+                supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
+            }
+            return supported;
+        }
+
+        @Override
+        public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+                @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return false;
+            }
+            try {
+                VibrationAttributes attr = new VibrationAttributes.Builder(
+                        attributes, effect).build();
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), effect)
+                        .combine();
+                return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
+            }
+            return false;
+        }
+
+        @Override
+        public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
+                @NonNull VibrationAttributes attributes) {
+            if (mService == null) {
+                Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
+                        .addVibrator(mVibratorInfo.getId(), vibe)
+                        .combine();
+                mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to vibrate.", e);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator manager service.");
+                return;
+            }
+            try {
+                mService.cancelVibrate(mToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
+            }
+        }
+
+        @Override
+        public boolean isVibrating() {
+            if (mService == null) {
+                Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
+                        + "; no vibrator service.");
+                return false;
+            }
+            try {
+                return mService.isVibrating(mVibratorInfo.getId());
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return false;
+        }
+
+        @Override
+        public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mContext == null) {
+                Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
+                return;
+            }
+            addVibratorStateListener(mContext.getMainExecutor(), listener);
+        }
+
+        @Override
+        public void addVibratorStateListener(
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            Objects.requireNonNull(executor);
+            if (mService == null) {
+                Log.w(TAG,
+                        "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
+                                + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // If listener is already registered, reject and return.
+                if (mListeners.containsKey(listener)) {
+                    Log.w(TAG, "Listener already registered.");
+                    return;
+                }
+                try {
+                    OnVibratorStateChangedListenerDelegate delegate =
+                            new OnVibratorStateChangedListenerDelegate(listener, executor);
+                    if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
+                        Log.w(TAG, "Failed to add vibrate state listener to vibrator "
+                                + mVibratorInfo.getId());
+                        return;
+                    }
+                    mListeners.put(listener, delegate);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+            Objects.requireNonNull(listener);
+            if (mService == null) {
+                Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                        + mVibratorInfo.getId() + "; no vibrator service.");
+                return;
+            }
+            synchronized (mLock) {
+                // Check if the listener is registered, otherwise will return.
+                if (mListeners.containsKey(listener)) {
+                    OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
+                    try {
+                        if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
+                                delegate)) {
+                            Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
+                                    + mVibratorInfo.getId());
+                            return;
+                        }
+                        mListeners.remove(listener);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3ffaa9e..a828077 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -96,14 +96,16 @@
         private final int mUid;
         private String mPackageWithHighestDrain;
         private boolean mSystemComponent;
+        private boolean mExcludeFromBatteryUsageStats;
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount,
-                BatteryStats.Uid batteryStatsUid) {
+                @NonNull BatteryStats.Uid batteryStatsUid) {
             super(customPowerComponentCount, customTimeComponentCount);
             mBatteryStatsUid = batteryStatsUid;
             mUid = batteryStatsUid.getUid();
         }
 
+        @NonNull
         public BatteryStats.Uid getBatteryStatsUid() {
             return mBatteryStatsUid;
         }
@@ -113,14 +115,6 @@
         }
 
         /**
-         * Creates a read-only object out of the Builder values.
-         */
-        @NonNull
-        public UidBatteryConsumer build() {
-            return new UidBatteryConsumer(this);
-        }
-
-        /**
          * Sets the name of the package owned by this UID that consumed the highest amount
          * of power since BatteryStats reset.
          */
@@ -131,12 +125,27 @@
         }
 
         /**
-         * Marks the UidBatteryConsumer as part of the system. For example,
-         * the UidBatteryConsumer with the UID {@link Process#BLUETOOTH_UID} is considered
-         * as a system component.
+         * Marks the UidBatteryConsumer for exclusion from the result set.
          */
-        public void setSystemComponent(boolean systemComponent) {
-            mSystemComponent = systemComponent;
+        public Builder excludeFromBatteryUsageStats() {
+            mExcludeFromBatteryUsageStats = true;
+            return this;
+        }
+
+        /**
+         * Returns true if this UidBatteryConsumer must be excluded from the
+         * BatteryUsageStats.
+         */
+        public boolean isExcludedFromBatteryUsageStats() {
+            return mExcludeFromBatteryUsageStats;
+        }
+
+        /**
+         * Creates a read-only object out of the Builder values.
+         */
+        @NonNull
+        public UidBatteryConsumer build() {
+            return new UidBatteryConsumer(this);
         }
     }
 }
diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java
new file mode 100644
index 0000000..94e567f
--- /dev/null
+++ b/core/java/android/os/UserBatteryConsumer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains power consumption data attributed to a {@link UserHandle}.
+ *
+ * {@hide}
+ */
+public class UserBatteryConsumer extends BatteryConsumer implements Parcelable {
+    private final int mUserId;
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    private UserBatteryConsumer(@NonNull UserBatteryConsumer.Builder builder) {
+        super(builder.mPowerComponentsBuilder.build());
+        mUserId = builder.mUserId;
+    }
+
+    private UserBatteryConsumer(Parcel in) {
+        super(new PowerComponents(in));
+        mUserId = in.readInt();
+    }
+
+    /**
+     * Writes the contents into a Parcel.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mUserId);
+    }
+
+    public static final Creator<UserBatteryConsumer> CREATOR =
+            new Creator<UserBatteryConsumer>() {
+                @Override
+                public UserBatteryConsumer createFromParcel(Parcel in) {
+                    return new UserBatteryConsumer(in);
+                }
+
+                @Override
+                public UserBatteryConsumer[] newArray(int size) {
+                    return new UserBatteryConsumer[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Builder for UserBatteryConsumer.
+     */
+    public static final class Builder extends BaseBuilder<Builder> {
+        private final int mUserId;
+        private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
+
+        Builder(int customPowerComponentCount, int customTimeComponentCount, int userId) {
+            super(customPowerComponentCount, customTimeComponentCount);
+            mUserId = userId;
+        }
+
+        /**
+         * Add a UidBatteryConsumer to this UserBatteryConsumer.
+         * <p>
+         * Calculated power and duration components of the added UID battery consumers
+         * are aggregated at the time the UserBatteryConsumer is built by the {@link #build()}
+         * method.
+         * </p>
+         */
+        public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
+            if (mUidBatteryConsumers == null) {
+                mUidBatteryConsumers = new ArrayList<>();
+            }
+            mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
+        }
+
+        /**
+         * Creates a read-only object out of the Builder values.
+         */
+        @NonNull
+        public UserBatteryConsumer build() {
+            if (mUidBatteryConsumers != null) {
+                for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
+                    UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
+                    mPowerComponentsBuilder.addPowerAndDuration(
+                            uidBatteryConsumer.mPowerComponentsBuilder);
+                }
+            }
+            return new UserBatteryConsumer(this);
+        }
+    }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 7d85d13..d6fa733 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -184,6 +184,15 @@
     }
 
     /**
+     * Return the ID of this vibrator.
+     *
+     * @return The id of the vibrator controlled by this service.
+     */
+    public int getId() {
+        return -1;
+    }
+
+    /**
      * Check whether the hardware has a vibrator.
      *
      * @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 1d5a587..5dd38b6 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -17,45 +17,123 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.util.Log;
 
 /**
- * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
- * a synchronized fashion.
+ * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * in a synchronized fashion.
+ * <p>
+ * If your process exits, any vibration you started will stop.
+ * </p>
  */
+@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
 public abstract class VibratorManager {
-    /** @hide */
-    protected static final String TAG = "VibratorManager";
+    private static final String TAG = "VibratorManager";
+
+    private final String mPackageName;
 
     /**
-     * {@hide}
+     * @hide to prevent subclassing from outside of the framework
      */
     public VibratorManager() {
+        mPackageName = ActivityThread.currentPackageName();
     }
 
     /**
-     * This method lists all available actuator ids, returning a possible empty list.
-     * If the device has only a single actuator, this should return a single entry with a
-     * default id.
+     * @hide to prevent subclassing from outside of the framework
+     */
+    protected VibratorManager(Context context) {
+        mPackageName = context.getOpPackageName();
+    }
+
+    /**
+     * List all available vibrator ids, returning a possible empty list.
+     *
+     * @return An array containing the ids of the vibrators available on the device.
      */
     @NonNull
     public abstract int[] getVibratorIds();
 
     /**
-    * Returns a Vibrator service for given id.
-    * This allows users to perform a vibration effect on a single actuator.
-    */
+     * Retrieve a single vibrator by id.
+     *
+     * @param vibratorId The id of the vibrator to be retrieved.
+     * @return The vibrator with given {@code vibratorId}, never null.
+     */
     @NonNull
     public abstract Vibrator getVibrator(int vibratorId);
 
     /**
-    * Returns the system default Vibrator service.
-    */
+     * Returns the system default Vibrator service.
+     */
     @NonNull
     public abstract Vibrator getDefaultVibrator();
 
     /**
-     * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
-     * to the respective actuator, in sync.
+     * Configure an always-on haptics effect.
+     *
+     * @hide
      */
-    public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+            @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
+        Log.w(TAG, "Always-on effects aren't supported");
+        return false;
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect an array of longs of times for which to turn the vibrator on or off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect) {
+        vibrate(effect, null);
+    }
+
+    /**
+     * Vibrate with a given combination of effects.
+     *
+     * <p>
+     * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
+     * VibrationEffect} to be played on one or more vibrators.
+     * </p>
+     *
+     * @param effect     an array of longs of times for which to turn the vibrator on or off.
+     * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+     *                   specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+     *                   {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+     *                   incoming calls.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public final void vibrate(@NonNull CombinedVibrationEffect effect,
+            @Nullable VibrationAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, effect, null, attributes);
+    }
+
+    /**
+     * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
+     * caller to specify the vibration is owned by someone else and set reason for vibration.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
+            String reason, @Nullable VibrationAttributes attributes);
+
+    /**
+     * Turn all the vibrators off.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public abstract void cancel();
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 0ff68fc..0589994 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -241,6 +241,13 @@
     }
 
     /**
+     * Checks if device supports V2 calls (e.g. PerUid).
+     */
+    public static boolean isV2Available() {
+        return nativeIsV2Available();
+    }
+
+    /**
      * Checks if Incremental installations are allowed.
      * A developer can disable Incremental installations by setting the property.
      */
@@ -439,6 +446,7 @@
 
     /* Native methods */
     private static native boolean nativeIsEnabled();
+    private static native boolean nativeIsV2Available();
     private static native boolean nativeIsIncrementalPath(@NonNull String path);
     private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
 }
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 137825
 
-moltmann@google.com
 evanseverson@google.com
 ntmyren@google.com
 zhanghai@google.com
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 85e9fdb..0e35ef9 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -693,13 +693,14 @@
         for (int usageNum = 0; usageNum < rawUsages.size(); usageNum++) {
             OpUsage usage = rawUsages.get(usageNum);
 
+            // If this attribution is a proxy, remove it
+            if (toRemoveProxies.contains(usage.toPackageAttr())) {
+                continue;
+            }
+
             // If this attribution has a special attribution, do not remove it
             if (specialAttributions.contains(usage.toPackageAttr())) {
                 deDuped.add(usage);
-            }
-
-            // If this attribution is a proxy, remove it
-            if (toRemoveProxies.contains(usage.toPackageAttr())) {
                 continue;
             }
 
diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS
index d09f351..b323468 100644
--- a/core/java/android/permissionpresenterservice/OWNERS
+++ b/core/java/android/permissionpresenterservice/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 137825
 
-moltmann@google.com
 evanseverson@google.com
 ntmyren@google.com
 zhanghai@google.com
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/print/pdf/OWNERS
+++ b/core/java/android/print/pdf/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
index 72f0983..28a24203 100644
--- a/core/java/android/printservice/recommendation/OWNERS
+++ b/core/java/android/printservice/recommendation/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 47273
 
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index faa90d9..86b20c4 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -350,6 +350,9 @@
             return cachedTypeface;
         }
 
+        Log.w(TAG, "Platform version of downloadable fonts is deprecated. Please use"
+                + " androidx version instead.");
+
         synchronized (sLock) {
             // It is possible that Font is loaded during the thread sleep time
             // re-check the cache to avoid re-loading the font
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef81ed7..f02e532 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10780,6 +10780,13 @@
                 "location_ignore_settings_package_whitelist";
 
         /**
+         * Whether to throttle location when the device is in doze and still.
+         * @hide
+         */
+        public static final String LOCATION_ENABLE_STATIONARY_THROTTLE =
+                "location_enable_stationary_throttle";
+
+        /**
         * Whether TV will switch to MHL port when a mobile device is plugged in.
         * (0 = false, 1 = true)
         * @hide
diff --git a/core/java/android/service/notification/NotificationListenerFilter.java b/core/java/android/service/notification/NotificationListenerFilter.java
index 6fdfaab..9de75ca 100644
--- a/core/java/android/service/notification/NotificationListenerFilter.java
+++ b/core/java/android/service/notification/NotificationListenerFilter.java
@@ -20,6 +20,7 @@
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
 
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -31,7 +32,8 @@
  */
 public class NotificationListenerFilter implements Parcelable {
     private int mAllowedNotificationTypes;
-    private ArraySet<String> mDisallowedPackages;
+    // VersionedPackage is holding the pkg name and pkg uid
+    private ArraySet<VersionedPackage> mDisallowedPackages;
 
     public NotificationListenerFilter() {
         mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS
@@ -41,7 +43,7 @@
         mDisallowedPackages = new ArraySet<>();
     }
 
-    public NotificationListenerFilter(int types, ArraySet<String> pkgs) {
+    public NotificationListenerFilter(int types, ArraySet<VersionedPackage> pkgs) {
         mAllowedNotificationTypes = types;
         mDisallowedPackages = pkgs;
     }
@@ -51,7 +53,8 @@
      */
     protected NotificationListenerFilter(Parcel in) {
         mAllowedNotificationTypes = in.readInt();
-        mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader());
+        mDisallowedPackages = (ArraySet<VersionedPackage>) in.readArraySet(
+                VersionedPackage.class.getClassLoader());
     }
 
     @Override
@@ -77,7 +80,7 @@
         return (mAllowedNotificationTypes & type) != 0;
     }
 
-    public boolean isPackageAllowed(String pkg) {
+    public boolean isPackageAllowed(VersionedPackage pkg) {
         return !mDisallowedPackages.contains(pkg);
     }
 
@@ -85,7 +88,7 @@
         return mAllowedNotificationTypes;
     }
 
-    public ArraySet<String> getDisallowedPackages() {
+    public ArraySet<VersionedPackage> getDisallowedPackages() {
         return mDisallowedPackages;
     }
 
@@ -93,10 +96,18 @@
         mAllowedNotificationTypes = types;
     }
 
-    public void setDisallowedPackages(ArraySet<String> pkgs) {
+    public void setDisallowedPackages(ArraySet<VersionedPackage> pkgs) {
         mDisallowedPackages = pkgs;
     }
 
+    public void removePackage(VersionedPackage pkg) {
+        mDisallowedPackages.remove(pkg);
+    }
+
+    public void addPackage(VersionedPackage pkg) {
+        mDisallowedPackages.add(pkg);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 64cddc3..f66f85b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -80,6 +80,10 @@
  *     &lt;intent-filter>
  *         &lt;action android:name="android.service.notification.NotificationListenerService" />
  *     &lt;/intent-filter>
+ *     &lt;meta-data
+ *               android:name="android.service.notification.default_filter_types"
+ *               android:value="1,2">
+ *           &lt;/meta-data>
  * &lt;/service></pre>
  *
  * <p>The service should wait for the {@link #onListenerConnected()} event
@@ -103,6 +107,21 @@
     private final String TAG = getClass().getSimpleName();
 
     /**
+     * The name of the {@code meta-data} tag containing a comma separated list of default
+     * integer notification types that should be provided to this listener. See
+     * {@link #FLAG_FILTER_TYPE_ONGOING},
+     * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
+     * and {@link #FLAG_FILTER_TYPE_SILENT}.
+     * <p>This value will only be read if the app has not previously specified a default type list,
+     * and if the user has not overridden the allowed types.</p>
+     * <p>An absent value means 'allow all types'.
+     * A present but empty value means 'allow no types'.</p>
+     *
+     */
+    public static final String META_DATA_DEFAULT_FILTER_TYPES
+            = "android.service.notification.default_filter_types";
+
+    /**
      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
      *     Normal interruption filter.
      */
@@ -254,23 +273,19 @@
     /**
      * A flag value indicating that this notification listener can see conversation type
      * notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
     /**
      * A flag value indicating that this notification listener can see altering type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ALERTING = 2;
     /**
      * A flag value indicating that this notification listener can see silent type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_SILENT = 4;
     /**
      * A flag value indicating that this notification listener can see important
      * ( > {@link NotificationManager#IMPORTANCE_MIN}) ongoing type notifications.
-     * @hide
      */
     public static final int FLAG_FILTER_TYPE_ONGOING = 8;
 
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index a750b68..87add57 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -95,6 +95,21 @@
     public static final String EXTRA_ERROR =
             "android.service.storage.extra.error";
 
+    /**
+     * {@link Bundle} key for a package name {@link String} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name";
+
+    /**
+     * {@link Bundle} key for a {@link Long} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_ANR_TIMEOUT_MS =
+            "android.service.storage.extra.anr_timeout_ms";
+
     /** @hide */
     @IntDef(flag = true, prefix = {"FLAG_SESSION_"},
         value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
@@ -162,6 +177,15 @@
         throw new UnsupportedOperationException("onFreeCacheRequested not implemented");
     }
 
+    /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long onGetAnrDelayMillis(@NonNull String packageName, int uid) {
+        throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented");
+    }
+
     @Override
     @NonNull
     public final IBinder onBind(@NonNull Intent intent) {
@@ -222,6 +246,19 @@
             });
         }
 
+        @Override
+        public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback)
+                throws RemoteException {
+            mHandler.post(() -> {
+                try {
+                    long timeoutMs = onGetAnrDelayMillis(packageName, uid);
+                    sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback);
+                } catch (Throwable t) {
+                    sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback);
+                }
+            });
+        }
+
         private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
             Bundle bundle = new Bundle();
             bundle.putString(EXTRA_SESSION_ID, sessionId);
@@ -230,5 +267,16 @@
             }
             callback.sendResult(bundle);
         }
+
+        private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable,
+                RemoteCallback callback) {
+            Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_PACKAGE_NAME, packageName);
+            bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs);
+            if (throwable != null) {
+                bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+            }
+            callback.sendResult(bundle);
+        }
     }
 }
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
index d06671b..2e0bd86 100644
--- a/core/java/android/service/storage/IExternalStorageService.aidl
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -32,4 +32,5 @@
         in RemoteCallback callback);
     void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes,
         in RemoteCallback callback);
+    void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
new file mode 100644
index 0000000..9d2e5b9
--- /dev/null
+++ b/core/java/android/service/wallpaper/EngineWindowPage.java
@@ -0,0 +1,96 @@
+/*
+ * 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.service.wallpaper;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * This class represents a page of a launcher page used by the wallpaper
+ * @hide
+ */
+public class EngineWindowPage {
+    private Bitmap mScreenShot;
+    private volatile long  mLastUpdateTime;
+    private Set<RectF> mCallbackAreas = new ArraySet<>();
+    private Map<RectF, WallpaperColors> mRectFColors = new ArrayMap<>();
+
+    /** should be locked extrnally */
+    public void addArea(RectF area) {
+        mCallbackAreas.add(area);
+    }
+
+    /** should be locked extrnally */
+    public void addWallpaperColors(RectF area, WallpaperColors colors) {
+        mCallbackAreas.add(area);
+        mRectFColors.put(area, colors);
+    }
+
+    /** get screenshot bitmap */
+    public Bitmap getBitmap() {
+        if (mScreenShot == null || mScreenShot.isRecycled()) return null;
+        return mScreenShot;
+    }
+
+    /** remove callbacks for an area */
+    public void removeArea(RectF area) {
+        mCallbackAreas.remove(area);
+        mRectFColors.remove(area);
+    }
+
+    /** set the last time the screenshot was updated */
+    public void setLastUpdateTime(long lastUpdateTime) {
+        mLastUpdateTime = lastUpdateTime;
+    }
+
+    /** get last screenshot time */
+    public long getLastUpdateTime() {
+        return mLastUpdateTime;
+    }
+
+    /** get colors for an area */
+    public WallpaperColors getColors(RectF rect) {
+        return mRectFColors.get(rect);
+    }
+
+    /** set the new bitmap version */
+    public void setBitmap(Bitmap screenShot) {
+        mScreenShot = screenShot;
+    }
+
+    /** get areas of interest */
+    public Set<RectF> getAreas() {
+        return mCallbackAreas;
+    }
+
+    /** run operations on this page */
+    public synchronized void execSync(Consumer<EngineWindowPage> run) {
+        run.accept(this);
+    }
+
+    /** nullify the area color */
+    public void removeColor(RectF colorArea) {
+        mRectFColors.remove(colorArea);
+    }
+}
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index f334d9d..f81ed34 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.graphics.RectF;
 import android.os.ParcelFileDescriptor;
 import android.service.wallpaper.IWallpaperEngine;
 import android.app.WallpaperColors;
@@ -28,4 +29,5 @@
     void engineShown(IWallpaperEngine engine);
     ParcelFileDescriptor setWallpaper(String name);
     void onWallpaperColorsChanged(in WallpaperColors colors, int displayId);
+    void onLocalWallpaperColorsChanged(in RectF area, in WallpaperColors colors, int displayId);
 }
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 90392e6..fbb449d 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,7 +16,10 @@
 
 package android.service.wallpaper;
 
+import android.app.ILocalWallpaperColorConsumer;
+import android.app.WallpaperColors;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.os.Bundle;
 
@@ -39,4 +42,6 @@
     void destroy();
     void setZoomOut(float scale);
     void scalePreview(in Rect positionInWindow);
+    void removeLocalColorsAreas(in List<RectF> regions);
+    void addLocalColorsAreas(in List<RectF> regions);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 82e0b4a..0b447d5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -23,6 +23,7 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -42,6 +43,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
@@ -53,6 +55,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.view.Display;
@@ -66,6 +70,8 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.MotionEvent;
+import android.view.PixelCopy;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.View;
@@ -83,7 +89,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 
 /**
@@ -115,7 +124,12 @@
     public static final String SERVICE_META_DATA = "android.service.wallpaper";
 
     static final String TAG = "WallpaperService";
-    static final boolean DEBUG = false;
+    static final boolean DEBUG = true;
+    static final float MIN_PAGE_ALLOWED_MARGIN = .05f;
+    private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64;
+    private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     private static final int DO_ATTACH = 10;
     private static final int DO_DETACH = 20;
@@ -134,6 +148,8 @@
     private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_SCALE_PREVIEW = 10110;
+    private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
+            Float.NEGATIVE_INFINITY);
 
     private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
 
@@ -158,6 +174,14 @@
      */
     public class Engine {
         IWallpaperEngineWrapper mIWallpaperEngine;
+        final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+        final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+
+        // 2D matrix [x][y] to represent a page of a portion of a window
+        EngineWindowPage[] mWindowPages = new EngineWindowPage[1];
+        Bitmap mLastScreenshot;
+        int mLastWindowPage = -1;
+        float mLastPageOffset = 0;
 
         // Copies from mIWallpaperEngine.
         HandlerCaller mCaller;
@@ -409,8 +433,8 @@
          */
         @VisibleForTesting
         public Engine(Supplier<Long> clockFunction, Handler handler) {
-           mClockFunction = clockFunction;
-           mHandler = handler;
+            mClockFunction = clockFunction;
+            mHandler = handler;
         }
 
         /**
@@ -448,6 +472,19 @@
         }
 
         /**
+         * Return whether the wallpaper is capable of extracting local colors in a rectangle area,
+         * Must implement without calling super:
+         * {@link #addLocalColorsAreas(List)}
+         * {@link #removeLocalColorsAreas(List)}
+         * When local colors change, call {@link #notifyLocalColorsChanged(List, List)}
+         * See {@link com.android.systemui.ImageWallpaper} for an example
+         * @hide
+         */
+        public boolean supportsLocalColorExtraction() {
+            return false;
+        }
+
+        /**
          * Returns true if this engine is running in preview mode -- that is,
          * it is being shown to the user before they select it as the actual
          * wallpaper.
@@ -691,6 +728,8 @@
                     Log.w(TAG, "Can't notify system because wallpaper connection "
                             + "was not established.");
                 }
+                resetWindowPages();
+                processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
             }
@@ -714,6 +753,28 @@
         }
 
         /**
+         * Send the changed local color areas for the connection
+         * @param regions
+         * @param colors
+         * @hide
+         */
+        public void notifyLocalColorsChanged(@NonNull List<RectF> regions,
+                @NonNull List<WallpaperColors> colors)
+                throws RuntimeException {
+            for (int i = 0; i < regions.size() && i < colors.size() && colors.get(i) != null; i++) {
+                try {
+                    mConnection.onLocalWallpaperColorsChanged(
+                            regions.get(i),
+                            colors.get(i),
+                            mDisplayContext.getDisplayId()
+                    );
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        /**
          * Sets internal engine state. Only for testing.
          * @param created {@code true} or {@code false}.
          * @hide
@@ -1068,7 +1129,9 @@
                         mIsCreating = false;
                         mSurfaceCreated = true;
                         if (redrawNeeded) {
+                            resetWindowPages();
                             mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
+                            processLocalColors(mPendingXOffset, mPendingXOffsetStep);
                         }
                         reposition();
                         mIWallpaperEngine.reportShown();
@@ -1209,6 +1272,7 @@
             if (!mDestroyed) {
                 mVisible = visible;
                 reportVisibility();
+                if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
             }
         }
 
@@ -1278,6 +1342,405 @@
                 } catch (RemoteException e) {
                 }
             }
+
+            // setup local color extraction data
+            processLocalColors(xOffset, xOffsetStep);
+        }
+
+        private void processLocalColors(float xOffset, float xOffsetStep) {
+            // implemented by the wallpaper
+            if (supportsLocalColorExtraction()) return;
+            if (DEBUG) {
+                Log.d(TAG, "processLocalColors " + xOffset + " of step "
+                        + xOffsetStep);
+            }
+            //below is the default implementation
+            if (!validStep(xOffsetStep)) {
+                if (DEBUG) {
+                    Log.w(TAG, "invalid offset step " + xOffsetStep);
+                }
+                return;
+            }
+
+            int xPage = Math.round(xOffset / xOffsetStep);
+            if (!shouldProcessPage(xPage, xOffset, xOffsetStep)) return;
+            mLastWindowPage = xPage;
+            mLastPageOffset = xOffset;
+            int xPages = Math.round(1 / xOffsetStep);
+            if (DEBUG) {
+                Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
+                Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+            }
+            EngineWindowPage current;
+            synchronized (mLock) {
+                if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
+                    mWindowPages = new EngineWindowPage[xPages];
+                    initWindowPages(mWindowPages, xOffsetStep);
+                }
+                if (mLocalColorsToAdd.size() != 0) {
+                    for (RectF colorArea : mLocalColorsToAdd) {
+                        if (!isValid(colorArea)) continue;
+                        mLocalColorAreas.add(colorArea);
+                        int colorPage = getRectFPage(colorArea, xOffsetStep);
+                        EngineWindowPage currentPage = mWindowPages[colorPage];
+                        if (currentPage == null) {
+                            currentPage = new EngineWindowPage();
+                            currentPage.addArea(colorArea);
+                            mWindowPages[colorPage] = currentPage;
+                        } else {
+                            currentPage.setLastUpdateTime(0);
+                            currentPage.removeColor(colorArea);
+                        }
+                    }
+                    mLocalColorsToAdd.clear();
+                }
+                if (xPage >= mWindowPages.length) {
+                    if (DEBUG) {
+                        Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
+                        Log.e(TAG, "error on page " + xPage + " out of " + xPages);
+                        Log.e(TAG,
+                                "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+                    }
+                    xPage = mWindowPages.length - 1;
+                }
+                current = mWindowPages[xPage];
+                if (current == null) {
+                    if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
+                    if (DEBUG) {
+                        Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+                    }
+                    current = new EngineWindowPage();
+                    mWindowPages[xPage] = current;
+                }
+            }
+            updatePage(current, xPage, xPages, xOffsetStep);
+        }
+
+        private boolean shouldProcessPage(int xPage, float xOffset, float xOffsetStep) {
+            float pageCenter = xOffsetStep * xPage + xOffsetStep / 2;
+
+            return (pageCenter > xOffset && pageCenter < mLastPageOffset)
+                || (pageCenter < xOffset && pageCenter > mLastPageOffset);
+        }
+
+        private void initWindowPages(EngineWindowPage[] windowPages, float step) {
+            for (int i = 0; i < windowPages.length; i++) {
+                windowPages[i] = new EngineWindowPage();
+            }
+            mLocalColorAreas.addAll(mLocalColorsToAdd);
+            mLocalColorsToAdd.clear();
+            for (RectF area: mLocalColorAreas) {
+                if (!isValid(area)) {
+                    mLocalColorAreas.remove(area);
+                    continue;
+                }
+                int pageNum = getRectFPage(area, step);
+                windowPages[pageNum].addArea(area);
+            }
+        }
+
+        void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
+                float xOffsetStep) {
+            // to save creating a runnable, check twice
+            long current = System.nanoTime() / 1_000_000;
+            AtomicLong lapsed = new AtomicLong(current - currentPage.getLastUpdateTime());
+            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+            currentPage.execSync((page) -> {
+                // just in case of race double check
+                long currentInner = System.nanoTime() / 1_000_000;
+                lapsed.set(currentInner - page.getLastUpdateTime());
+                page.setLastUpdateTime(currentInner);
+            });
+            long lastUpdate = currentPage.getLastUpdateTime();
+            if (lapsed.get() < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+            Surface surface = mSurfaceHolder.getSurface();
+            boolean widthIsLarger =
+                    mSurfaceControl.getWidth() > mSurfaceControl.getHeight();
+            int smaller = widthIsLarger ? mSurfaceControl.getWidth()
+                    : mSurfaceControl.getHeight();
+            float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller;
+            int width = (int) (ratio * mSurfaceControl.getWidth());
+            int height = (int) (ratio * mSurfaceControl.getHeight());
+            if (width <= 0 || height <= 0) {
+                return;
+            }
+            Bitmap screenShot = Bitmap.createBitmap(width, height,
+                    Bitmap.Config.ARGB_8888);
+            final Bitmap finalScreenShot = screenShot;
+            Trace.beginSection("WallpaperService#pixelCopy");
+            PixelCopy.request(surface, screenShot, (res) -> {
+                Trace.endSection();
+                if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
+                if (res != PixelCopy.SUCCESS) {
+                    currentPage.execSync((p) -> {
+                        // reset the time
+                        p.setLastUpdateTime(lastUpdate);
+                        // assign the last bitmap taken for now
+                        p.setBitmap(mLastScreenshot);
+                    });
+                    return;
+                }
+                mLastScreenshot = finalScreenShot;
+                // going to hold this lock for a while
+                currentPage.execSync((p) -> {
+                    p.setBitmap(finalScreenShot);
+                });
+                updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+            }, mHandler);
+
+        }
+        // locked by the passed page
+        private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
+                float xOffsetStep) {
+            if (page.getBitmap() == null) return;
+            if (DEBUG) {
+                Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+                        + page.getAreas().size() + " and bitmap size of "
+                        + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
+            }
+            for (RectF area: page.getAreas()) {
+                RectF subArea = generateSubRect(area, pageIndx, numPages);
+                Bitmap b = page.getBitmap();
+                int x = Math.round(b.getWidth() * subArea.left);
+                int y = Math.round(b.getHeight() * subArea.top);
+                int width = Math.round(b.getWidth() * subArea.width());
+                int height = Math.round(b.getHeight() * subArea.height());
+                Bitmap target;
+                try {
+                    target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error creating page local color bitmap", e);
+                    continue;
+                }
+                WallpaperColors color = WallpaperColors.fromBitmap(target);
+                target.recycle();
+                WallpaperColors currentColor = page.getColors(area);
+
+                if (DEBUG) {
+                    Log.d(TAG, "getting local bitmap area x " + x + " y " + y
+                            + " width " + width + " height " + height + " for sub area " + subArea
+                            + " and with page " + pageIndx + " of " + numPages);
+
+                }
+                if (currentColor == null || !color.equals(currentColor)) {
+                    page.addWallpaperColors(area, color);
+                    if (DEBUG) {
+                        Log.d(TAG, "onLocalWallpaperColorsChanged"
+                                + " local color callback for area" + area + " for page " + pageIndx
+                                + " of " + numPages);
+                    }
+                    try {
+                        mConnection.onLocalWallpaperColorsChanged(area, color,
+                                mDisplayContext.getDisplayId());
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+                    }
+                }
+            }
+        }
+
+        private RectF generateSubRect(RectF in, int pageInx, int numPages) {
+            float minLeft = (float) (pageInx) / (float) (numPages);
+            float maxRight = (float) (pageInx + 1) / (float) (numPages);
+            float left = in.left;
+            float right = in.right;
+
+            // bound rect
+            if (left < minLeft) left = minLeft;
+            if (right > maxRight) right = maxRight;
+
+            // scale up the sub area then trim
+            left = (left * (float) numPages) % 1f;
+            right = (right * (float) numPages) % 1f;
+            if (right == 0f) {
+                right = 1f;
+            }
+
+            return new RectF(left, in.top, right, in.bottom);
+        }
+
+        private void resetWindowPages() {
+            if (supportsLocalColorExtraction()) return;
+            mLastWindowPage = -1;
+            synchronized (mLock) {
+                for (int i = 0; i < mWindowPages.length; i++) {
+                    EngineWindowPage page = mWindowPages[i];
+                    if (page != null) {
+                        page.execSync((p) -> {
+                            p.setLastUpdateTime(0L);
+                        });
+                    }
+                }
+            }
+        }
+
+        private int getRectFPage(RectF area, float step) {
+            if (!isValid(area)) return 0;
+            if (!validStep(step)) return 0;
+            int pages = Math.round(1 / step);
+            int page = Math.round(area.centerX() * pages);
+            if (page == pages) return pages - 1;
+            if (page == mWindowPages.length) page = mWindowPages.length - 1;
+            return page;
+        }
+
+        /**
+         * Add local colors areas of interest
+         * @param regions list of areas
+         * @hide
+         */
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            if (supportsLocalColorExtraction()) return;
+            if (DEBUG) {
+                Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
+            }
+            float step = mPendingXOffsetStep;
+
+            List<WallpaperColors> colors = getLocalWallpaperColors(regions);
+            synchronized (mLock) {
+                if (!validStep(step)) {
+                    mLocalColorsToAdd.addAll(regions);
+                    return;
+                }
+                for (int i = 0; i < regions.size(); i++) {
+                    RectF area = regions.get(i);
+                    if (!isValid(area)) continue;
+                    int pageInx = getRectFPage(area, step);
+                    // no page should be null
+                    EngineWindowPage page = mWindowPages[pageInx];
+
+                    if (page != null) {
+                        mLocalColorAreas.add(area);
+                        page.addArea(area);
+                        WallpaperColors color = colors.get(i);
+                        if (color != null && !color.equals(page.getColors(area))) {
+                            page.execSync(p -> {
+                                p.addWallpaperColors(area, color);
+                            });
+                        }
+                    } else {
+                        mLocalColorsToAdd.add(area);
+                    }
+                }
+            }
+
+            for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
+                try {
+                    mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
+                            mDisplayContext.getDisplayId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+                    return;
+                }
+            }
+        }
+
+        /**
+         * Remove local colors areas of interest if they exist
+         * @param regions list of areas
+         * @hide
+         */
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            if (supportsLocalColorExtraction()) return;
+            synchronized (mLock) {
+                float step = mPendingXOffsetStep;
+                mLocalColorsToAdd.removeAll(regions);
+                mLocalColorAreas.removeAll(regions);
+                if (!validStep(step)) {
+                    return;
+                }
+                for (int i = 0; i < regions.size(); i++) {
+                    RectF area = regions.get(i);
+                    if (!isValid(area)) continue;
+                    int pageInx = getRectFPage(area, step);
+                    // no page should be null
+                    EngineWindowPage page = mWindowPages[pageInx];
+                    if (page != null) {
+                        page.execSync(p -> {
+                            p.removeArea(area);
+                        });
+                    }
+                }
+            }
+        }
+
+        private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
+            ArrayList<WallpaperColors> colors = new ArrayList<>(areas.size());
+            float step = mPendingXOffsetStep;
+            if (!validStep(step)) {
+                if (DEBUG) Log.d(TAG, "invalid step size " + step);
+                step = 1.0f;
+            }
+            for (int i = 0; i < areas.size(); i++) {
+                RectF currentArea = areas.get(i);
+                EngineWindowPage page;
+                RectF area;
+                int pageIndx;
+                synchronized (mLock) {
+                    pageIndx = getRectFPage(currentArea, step);
+                    if (mWindowPages.length == 0 || pageIndx < 0
+                            || pageIndx > mWindowPages.length || !isValid(currentArea)) {
+                        colors.add(null);
+                        continue;
+                    }
+                    area = generateSubRect(currentArea, pageIndx, mWindowPages.length);
+                    page = mWindowPages[pageIndx];
+                }
+                if (page == null) {
+                    colors.add(null);
+                    continue;
+                }
+                float finalStep = step;
+                int finalPageIndx = pageIndx;
+                Bitmap screenShot = page.getBitmap();
+                if (screenShot == null || screenShot.isRecycled()) {
+                    if (DEBUG) {
+                        Log.d(TAG, "invalid bitmap " + screenShot
+                                + " for page " + finalPageIndx);
+                    }
+                    page.setLastUpdateTime(0);
+                    colors.add(null);
+                    continue;
+                }
+                Bitmap b = screenShot;
+                Rect subImage = new Rect(
+                        Math.round(area.left * b.getWidth() / finalStep),
+                        Math.round(area.top * b.getHeight()),
+                        Math.round(area.right * b.getWidth() / finalStep),
+                        Math.round(area.bottom * b.getHeight())
+                );
+                subImage = fixRect(b, subImage);
+                if (DEBUG) {
+                    Log.d(TAG, "getting subbitmap of " + subImage.toString()
+                            + " for RectF " + area.toString()
+                            + " screenshot width " + screenShot.getWidth() + " height "
+                            + screenShot.getHeight());
+                }
+                Bitmap colorImg = Bitmap.createBitmap(screenShot,
+                        subImage.left, subImage.top, subImage.width(), subImage.height());
+                if (DEBUG) {
+                    Log.d(TAG, "created bitmap " + colorImg.getWidth() + ", "
+                            + colorImg.getHeight());
+                }
+                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+                colors.add(color);
+            }
+            return colors;
+        }
+
+        // fix the rect to be included within the bounds of the bitmap
+        private Rect fixRect(Bitmap b, Rect r) {
+            r.left =  r.left >= r.right || r.left >= b.getWidth() || r.left > 0
+                    ? 0
+                    : r.left;
+            r.right =  r.left >= r.right || r.right > b.getWidth()
+                    ? b.getWidth()
+                    : r.right;
+            return r;
+        }
+
+        private boolean validStep(float step) {
+            return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
         }
 
         void doCommand(WallpaperCommand cmd) {
@@ -1371,6 +1834,16 @@
         };
     }
 
+    private boolean isValid(RectF area) {
+        boolean valid = area.bottom > area.top && area.left < area.right
+                && LOCAL_COLOR_BOUNDS.contains(area);
+        return valid;
+    }
+
+    private boolean inRectFRange(float number) {
+        return number >= 0f && number <= 1f;
+    }
+
     class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
             implements HandlerCaller.Callback {
         private final HandlerCaller mCaller;
@@ -1477,6 +1950,14 @@
             mCaller.sendMessage(msg);
         }
 
+        public void addLocalColorsAreas(List<RectF> regions) {
+            mEngine.addLocalColorsAreas(regions);
+        }
+
+        public void removeLocalColorsAreas(List<RectF> regions) {
+            mEngine.removeLocalColorsAreas(regions);
+        }
+
         public void destroy() {
             Message msg = mCaller.obtainMessage(DO_DETACH);
             mCaller.sendMessage(msg);
@@ -1516,14 +1997,15 @@
             }
             switch (message.what) {
                 case DO_ATTACH: {
+                    Engine engine = onCreateEngine();
+                    mEngine = engine;
                     try {
                         mConnection.attachEngine(this, mDisplayId);
                     } catch (RemoteException e) {
+                        engine.detach();
                         Log.w(TAG, "Wallpaper host disappeared", e);
                         return;
                     }
-                    Engine engine = onCreateEngine();
-                    mEngine = engine;
                     mActiveEngines.add(engine);
                     engine.attach(this);
                     return;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 990b7bd..dad932c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -123,14 +123,6 @@
     boolean outOfMemory(IWindow window);
 
     /**
-     * Give the window manager a hint of the part of the window that is
-     * completely transparent, allowing it to work with the surface flinger
-     * to optimize compositing of this part of the window.
-     */
-    @UnsupportedAppUsage
-    oneway void setTransparentRegion(IWindow window, in Region region);
-
-    /**
      * Tell the window manager about the content and visible insets of the
      * given window, which can be used to adjust the <var>outContentInsets</var>
      * and <var>outVisibleInsets</var> values returned by
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index cbc0479..20f0598 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -32,7 +32,6 @@
 
     private static native long nativeCreate();
     private static native void nativeDestroy(long ptr);
-    private static native void nativeKill(long ptr);
 
     /** Create a new connection with the surface flinger. */
     @UnsupportedAppUsage
@@ -44,22 +43,22 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mNativeClient != 0) {
-                nativeDestroy(mNativeClient);
-            }
+            kill();
         } finally {
             super.finalize();
         }
     }
 
     /**
-     * Forcibly detach native resources associated with this object.
-     * Unlike destroy(), after this call any surfaces that were created
-     * from the session will no longer work.
+     * Remove the reference to the native Session object. The native object may still exist if
+     * there are other references to it, but it cannot be accessed from this Java object anymore.
      */
     @UnsupportedAppUsage
     public void kill() {
-        nativeKill(mNativeClient);
+        if (mNativeClient != 0) {
+            nativeDestroy(mNativeClient);
+            mNativeClient = 0;
+        }
     }
 }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 70ec2d4..6eba83f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1083,7 +1083,12 @@
 
                 if (creating) {
                     updateOpaqueFlag();
-                    mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
+                    final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+                    if (mUseBlastAdapter) {
+                        createBlastSurfaceControls(viewRoot, name);
+                    } else {
+                        mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
+                    }
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -1220,53 +1225,77 @@
      * out, the old surface can be persevered until the new one has drawn by keeping the reference
      * of the old SurfaceControl alive.
      */
-    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
-        final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
-
-        SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+    private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot, String name) {
+        final SurfaceControl previousSurfaceControl = mSurfaceControl;
+        mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                 .setName(name)
                 .setLocalOwnerView(this)
                 .setParent(viewRoot.getBoundsLayer())
-                .setCallsite("SurfaceView.updateSurface");
+                .setCallsite("SurfaceView.updateSurface")
+                .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+                .setFlags(mSurfaceFlags)
+                .setFormat(mFormat)
+                .build();
+        mBackgroundControl = createBackgroundControl(name);
+        return previousSurfaceControl;
+    }
 
-        final SurfaceControl previousSurfaceControl;
-        if (mUseBlastAdapter) {
-            mSurfaceControl = builder
+    private SurfaceControl createBackgroundControl(String name) {
+        return new SurfaceControl.Builder(mSurfaceSession)
+        .setName("Background for " + name)
+        .setLocalOwnerView(this)
+        .setOpaque(true)
+        .setColorLayer()
+        .setParent(mSurfaceControl)
+        .setCallsite("SurfaceView.updateSurface")
+        .build();
+    }
+
+    // We don't recreate the surface controls but only recreate the adapter. Since the blast layer
+    // is still alive, the old buffers will continue to be presented until replaced by buffers from
+    // the new adapter. This means we do not need to track the old surface control and destroy it
+    // after the client has drawn to avoid any flickers.
+    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+        if (mSurfaceControl == null) {
+            mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                    .setName(name)
+                    .setLocalOwnerView(this)
+                    .setParent(viewRoot.getBoundsLayer())
+                    .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
-            previousSurfaceControl = mBlastSurfaceControl;
+        }
+
+        if (mBlastSurfaceControl == null) {
             mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name + "(BLAST)")
                     .setLocalOwnerView(this)
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
                     .setParent(mSurfaceControl)
                     .setFlags(mSurfaceFlags)
                     .setHidden(false)
                     .setBLASTLayer()
                     .setCallsite("SurfaceView.updateSurface")
                     .build();
-            mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                    mSurfaceHeight, mFormat, true /* TODO */);
         } else {
-            previousSurfaceControl = mSurfaceControl;
-            mSurfaceControl = builder
-                    .setBufferSize(mSurfaceWidth, mSurfaceHeight)
-                    .setFlags(mSurfaceFlags)
-                    .setFormat(mFormat)
-                    .build();
-            mBlastSurfaceControl = null;
-            mBlastBufferQueue = null;
+            // update blast layer
+            mTmpTransaction
+                    .setOpaque(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+                    .setSecure(mBlastSurfaceControl, (mSurfaceFlags & SurfaceControl.SECURE) != 0)
+                    .show(mBlastSurfaceControl)
+                    .apply();
         }
-        mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
-            .setName("Background for " + name)
-            .setLocalOwnerView(this)
-            .setOpaque(true)
-            .setColorLayer()
-            .setParent(mSurfaceControl)
-            .setCallsite("SurfaceView.updateSurface")
-            .build();
 
-        return previousSurfaceControl;
+        if (mBackgroundControl == null) {
+            mBackgroundControl = createBackgroundControl(name);
+        }
+
+        // Always recreate the IGBP for compatibility. This can be optimized in the future but
+        // the behavior change will need to be gated by SDK version.
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+        }
+        mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
+                mSurfaceHeight, mFormat, true /* TODO */);
     }
 
     private void onDrawFinished() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e1ccc51..ba78f96 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -740,6 +740,7 @@
  * @attr ref android.R.styleable#View_alpha
  * @attr ref android.R.styleable#View_background
  * @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_clipToOutline
  * @attr ref android.R.styleable#View_contentDescription
  * @attr ref android.R.styleable#View_drawingCacheQuality
  * @attr ref android.R.styleable#View_duplicateParentState
@@ -5968,6 +5969,9 @@
                 case R.styleable.View_scrollCaptureHint:
                     setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO)));
                     break;
+                case R.styleable.View_clipToOutline:
+                    setClipToOutline(a.getBoolean(attr, false));
+                    break;
             }
         }
 
@@ -17921,6 +17925,8 @@
      *
      * @see #setOutlineProvider(ViewOutlineProvider)
      * @see #getClipToOutline()
+     *
+     * @attr ref android.R.styleable#View_clipToOutline
      */
     @RemotableViewMethod
     public void setClipToOutline(boolean clipToOutline) {
@@ -22158,6 +22164,10 @@
      * and hardware acceleration.
      */
     boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+        // Clear the overscroll effect:
+        // TODO: Use internal API instead of overriding the existing RenderEffect
+        setRenderEffect(null);
+
         final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
         /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
          *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index acc77b5..f1f6786 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1375,6 +1375,7 @@
                 final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
                 mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                         attrs.getTitle().toString());
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
                 updateColorModeIfNeeded(attrs.getColorMode());
                 updateForceDarkMode();
                 if (mAttachInfo.mThreadedRenderer != null) {
@@ -1898,11 +1899,12 @@
     }
 
     private void setBoundsLayerCrop(Transaction t) {
-        // mWinFrame is already adjusted for surface insets. So offset it and use it as
-        // the cropping bounds.
-        mTempBoundsRect.set(mWinFrame);
-        mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
-                mWindowAttributes.surfaceInsets.top);
+        // Adjust of insets and update the bounds layer so child surfaces do not draw into
+        // the surface inset region.
+        mTempBoundsRect.set(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+        mTempBoundsRect.inset(mWindowAttributes.surfaceInsets.left,
+                mWindowAttributes.surfaceInsets.top,
+                mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
         t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
     }
 
@@ -1913,25 +1915,18 @@
     private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
         if (mBoundsLayer != null) {
             setBoundsLayerCrop(t);
-            t.deferTransactionUntil(mBoundsLayer, getSurfaceControl(),
-                mSurface.getNextFrameNumber());
             return true;
         }
         return false;
     }
 
-    private void prepareSurfaces(boolean sizeChanged) {
+    private void prepareSurfaces() {
         final SurfaceControl.Transaction t = mTransaction;
         final SurfaceControl sc = getSurfaceControl();
         if (!sc.isValid()) return;
 
-        boolean applyTransaction = updateBoundsLayer(t);
-        if (sizeChanged) {
-            applyTransaction = true;
-            t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
-        }
-        if (applyTransaction) {
-            t.apply();
+        if (updateBoundsLayer(t)) {
+              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
         }
     }
 
@@ -1947,6 +1942,10 @@
             mBlastBufferQueue.destroy();
             mBlastBufferQueue = null;
         }
+
+        if (mAttachInfo.mThreadedRenderer != null) {
+            mAttachInfo.mThreadedRenderer.setSurfaceControl(null);
+        }
     }
 
     /**
@@ -2842,8 +2841,7 @@
                         mScroller.abortAnimation();
                     }
                     // Our surface is gone
-                    if (mAttachInfo.mThreadedRenderer != null &&
-                            mAttachInfo.mThreadedRenderer.isEnabled()) {
+                    if (isHardwareEnabled()) {
                         mAttachInfo.mThreadedRenderer.destroy();
                     }
                 } else if ((surfaceReplaced
@@ -3036,7 +3034,7 @@
             // stopping, but on the client side it doesn't get stopped since it's restarted quick
             // enough. WMS doesn't want to keep around old children since they will leak when the
             // client creates new children.
-            prepareSurfaces(surfaceSizeChanged);
+            prepareSurfaces();
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3064,10 +3062,15 @@
                 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                     mPreviousTransparentRegion.set(mTransparentRegion);
                     mFullRedrawNeeded = true;
-                    // reconfigure window manager
-                    try {
-                        mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
-                    } catch (RemoteException e) {
+                    // TODO: Ideally we would do this in prepareSurfaces,
+                    // but prepareSurfaces is currently working under
+                    // the assumption that we paused the render thread
+                    // via the WM relayout code path. We probably eventually
+                    // want to synchronize transparent region hint changes
+                    // with draws.
+                    SurfaceControl sc = getSurfaceControl();
+                    if (sc.isValid()) {
+                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
                     }
                 }
             }
@@ -3924,8 +3927,15 @@
         };
     }
 
+    /**
+     * @hide
+     */
+    public boolean isHardwareEnabled() {
+        return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
+    }
+
     private boolean addFrameCompleteCallbackIfNeeded() {
-        if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) {
+        if (!isHardwareEnabled()) {
             return false;
         }
 
@@ -4269,7 +4279,7 @@
 
         boolean useAsyncReport = false;
         if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
-            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
+            if (isHardwareEnabled()) {
                 // If accessibility focus moved, always invalidate the root.
                 boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
                 mInvalidateRootRequested = false;
@@ -7646,6 +7656,9 @@
                     mSurface.transferFrom(blastSurface);
                 }
             }
+            if (mAttachInfo.mThreadedRenderer != null) {
+                mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
+            }
         } else {
             destroySurface();
         }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af18293..4ecdd78 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1703,6 +1703,30 @@
     public abstract void setBackgroundDrawable(Drawable drawable);
 
     /**
+     * Blurs the screen behind the window within the bounds of the window.
+     *
+     * The density of the blur is set by the blur radius. The radius defines the size
+     * of the neighbouring area, from which pixels will be averaged to form the final
+     * color for each pixel. The operation approximates a Gaussian blur.
+     * A radius of 0 means no blur. The higher the radius, the denser the blur.
+     *
+     * The window background drawable is drawn on top of the blurred region. The blur
+     * region bounds and rounded corners will mimic those of the background drawable.
+     *
+     * For the blur region to be visible, the window has to be translucent. See
+     * {@link android.R.styleable#Window_windowIsTranslucent}.
+     *
+     * Note the difference with {@link android.view.WindowManager.LayoutParams#blurBehindRadius},
+     * which blurs the whole screen behind the window. Background blur blurs the screen behind
+     * only within the bounds of the window.
+     *
+     * @param blurRadius The blur radius to use for window background blur in pixels
+     *
+     * @see android.R.styleable#Window_windowBackgroundBlurRadius
+     */
+    public void setBackgroundBlurRadius(int blurRadius) {}
+
+    /**
      * Set the value for a drawable feature of this window, from a resource
      * identifier.  You must have called requestFeature(featureId) before
      * calling this function.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 3aedda1..39d3c01 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -314,10 +314,6 @@
     }
 
     @Override
-    public void setTransparentRegion(android.view.IWindow window, android.graphics.Region region) {
-    }
-
-    @Override
     public void setInsets(android.view.IWindow window, int touchableInsets,
             android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets,
             android.graphics.Region touchableRegion) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 794181e..decbf8c 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -200,6 +200,34 @@
             "android.view.autofill.extra.AUTHENTICATION_RESULT";
 
     /**
+     * Intent extra: The optional boolean extra field provided by the
+     * {@link android.service.autofill.AutofillService} accompanying the {@link
+     * android.service.autofill.Dataset} result of an authentication operation.
+     *
+     * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a
+     * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also
+     * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}.
+     * That means if the user clears the field values, the autofill suggestion will show up again
+     * with the new authenticated Dataset.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior
+     * that if the Dataset being authenticated is a pinned dataset (see
+     * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be
+     * replaced.
+     *
+     * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to
+     * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether
+     * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to
+     * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the
+     * returned Dataset will not replace the old dataset from the existing
+     * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not
+     * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is
+     * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}.
+     */
+    public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET =
+            "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET";
+
+    /**
      * Intent extra: The optional extras provided by the
      * {@link android.service.autofill.AutofillService}.
      *
@@ -1755,6 +1783,11 @@
             if (newClientState != null) {
                 responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
             }
+            if (data.getExtras().containsKey(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+                responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                        data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                                false));
+            }
             try {
                 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
                         mContext.getUserId());
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 90c8e17..7b2bb73 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,7 +115,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -1083,19 +1082,6 @@
         }
     }
 
-    private static class ImeThreadFactory implements ThreadFactory {
-        private final String mThreadName;
-
-        ImeThreadFactory(String name) {
-            mThreadName = name;
-        }
-
-        @Override
-        public Thread newThread(Runnable r) {
-            return new Thread(r, mThreadName);
-        }
-    }
-
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index c10ffbe..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -17,19 +17,29 @@
 package android.widget;
 
 import android.annotation.ColorInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
 import android.os.Build;
+import android.util.AttributeSet;
 import android.view.animation.AnimationUtils;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class performs the graphical effect used at the edges of scrollable widgets
  * when the user scrolls beyond the content bounds in 2D space.
@@ -55,6 +65,24 @@
      */
     public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
 
+    /**
+     * Use a color edge glow for the edge effect. From XML, use
+     * <code>android:edgeEffectType="glow"</code>.
+     */
+    public static final int TYPE_GLOW = 0;
+
+    /**
+     * Use a stretch for the edge effect. From XML, use
+     * <code>android:edgeEffectType="stretch"</code>.
+     */
+    public static final int TYPE_STRETCH = 1;
+
+    /** @hide */
+    @IntDef({TYPE_GLOW, TYPE_STRETCH})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EdgeEffectType {
+    }
+
     @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "EdgeEffect";
 
@@ -89,11 +117,14 @@
     private float mGlowAlpha;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private float mGlowScaleY;
+    private float mDistance;
 
     private float mGlowAlphaStart;
     private float mGlowAlphaFinish;
     private float mGlowScaleYStart;
     private float mGlowScaleYFinish;
+    private float mDistanceStart;
+    private float mDistanceFinish;
 
     private long mStartTime;
     private float mDuration;
@@ -121,17 +152,31 @@
     private float mBaseGlowScale;
     private float mDisplacement = 0.5f;
     private float mTargetDisplacement = 0.5f;
+    private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW;
+    private Matrix mTmpMatrix = null;
+    private float[] mTmpPoints = null;
 
     /**
      * Construct a new EdgeEffect with a theme appropriate for the provided context.
      * @param context Context used to provide theming and resource information for the EdgeEffect
      */
     public EdgeEffect(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Construct a new EdgeEffect with a theme appropriate for the provided context.
+     * @param context Context used to provide theming and resource information for the EdgeEffect
+     * @param attrs The attributes of the XML tag that is inflating the view
+     */
+    public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
         mPaint.setAntiAlias(true);
         final TypedArray a = context.obtainStyledAttributes(
-                com.android.internal.R.styleable.EdgeEffect);
+                attrs, com.android.internal.R.styleable.EdgeEffect);
         final int themeColor = a.getColor(
                 com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
+        mEdgeEffectType = a.getInt(
+                com.android.internal.R.styleable.EdgeEffect_edgeEffectType, TYPE_GLOW);
         a.recycle();
         mPaint.setColor((themeColor & 0xffffff) | 0x33000000);
         mPaint.setStyle(Paint.Style.FILL);
@@ -211,11 +256,18 @@
     public void onPull(float deltaDistance, float displacement) {
         final long now = AnimationUtils.currentAnimationTimeMillis();
         mTargetDisplacement = displacement;
-        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration
+                && mEdgeEffectType == TYPE_GLOW) {
             return;
         }
         if (mState != STATE_PULL) {
-            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+            if (mEdgeEffectType == TYPE_STRETCH) {
+                // Restore the mPullDistance to the fraction it is currently showing -- we want
+                // to "catch" the current stretch value.
+                mPullDistance = mDistance;
+            } else {
+                mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
+            }
         }
         mState = STATE_PULL;
 
@@ -223,6 +275,7 @@
         mDuration = PULL_TIME;
 
         mPullDistance += deltaDistance;
+        mDistanceStart = mDistanceFinish = mDistance = Math.max(0f, mPullDistance);
 
         final float absdd = Math.abs(deltaDistance);
         mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
@@ -242,6 +295,65 @@
     }
 
     /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} after this
+     * and draw the results accordingly. This works similarly to {@link #onPull(float, float)},
+     * but returns the amount of <code>deltaDistance</code> that has been consumed. If the
+     * {@link #getDistance()} is currently 0 and <code>deltaDistance</code> is negative, this
+     * function will return 0 and the drawn value will remain unchanged.
+     *
+     * This method can be used to reverse the effect from a pull or absorb and partially consume
+     * some of a motion:
+     *
+     * <pre class="prettyprint">
+     *     if (deltaY < 0) {
+     *         float consumed = edgeEffect.onPullDistance(deltaY / getHeight(), x / getWidth());
+     *         deltaY -= consumed * getHeight();
+     *         if (edgeEffect.getDistance() == 0f) edgeEffect.onRelease();
+     *     }
+     * </pre>
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     * @param displacement The displacement from the starting side of the effect of the point
+     *                     initiating the pull. In the case of touch this is the finger position.
+     *                     Values may be from 0-1.
+     * @return The amount of <code>deltaDistance</code> that was consumed, a number between
+     * 0 and <code>deltaDistance</code>.
+     */
+    public float onPullDistance(float deltaDistance, float displacement) {
+        float finalDistance = Math.max(0f, deltaDistance + mDistance);
+        float delta = finalDistance - mDistance;
+        if (delta == 0f && mDistance == 0f) {
+            return 0f; // No pull, don't do anything.
+        }
+
+        if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) {
+            // Catch the edge glow in the middle of an animation.
+            mPullDistance = mDistance;
+            mState = STATE_PULL;
+        }
+        onPull(delta, displacement);
+        return delta;
+    }
+
+    /**
+     * Returns the pull distance needed to be released to remove the showing effect.
+     * It is determined by the {@link #onPull(float, float)} <code>deltaDistance</code> and
+     * any animating values, including from {@link #onAbsorb(int)} and {@link #onRelease()}.
+     *
+     * This can be used in conjunction with {@link #onPullDistance(float, float)} to
+     * release the currently showing effect.
+     *
+     * @return The pull distance that must be released to remove the showing effect.
+     */
+    public float getDistance() {
+        return mDistance;
+    }
+
+    /**
      * Call when the object is released after being pulled.
      * This will begin the "decay" phase of the effect. After calling this method
      * the host view should {@link android.view.View#invalidate()} and thereby
@@ -257,9 +369,11 @@
         mState = STATE_RECEDE;
         mGlowAlphaStart = mGlowAlpha;
         mGlowScaleYStart = mGlowScaleY;
+        mDistanceStart = mDistance;
 
         mGlowAlphaFinish = 0.f;
         mGlowScaleYFinish = 0.f;
+        mDistanceFinish = 0.f;
 
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
         mDuration = RECEDE_TIME;
@@ -286,7 +400,7 @@
         // nearly invisible.
         mGlowAlphaStart = GLOW_ALPHA_START;
         mGlowScaleYStart = Math.max(mGlowScaleY, 0.f);
-
+        mDistanceStart = mDistance;
 
         // Growth for the size of the glow should be quadratic to properly
         // respond
@@ -297,6 +411,9 @@
         mGlowAlphaFinish = Math.max(
                 mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
         mTargetDisplacement = 0.5f;
+
+        // Use glow values to estimate the absorption for stretch distance.
+        mDistanceFinish = calculateDistanceFromGlowValues(mGlowScaleYFinish, mGlowAlphaFinish);
     }
 
     /**
@@ -309,6 +426,17 @@
     }
 
     /**
+     * Sets the edge effect type to use. The default without a theme attribute set is
+     * {@link EdgeEffect#TYPE_GLOW}.
+     *
+     * @param type The edge effect type to use.
+     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+     */
+    public void setType(@EdgeEffectType int type) {
+        mEdgeEffectType = type;
+    }
+
+    /**
      * Set or clear the blend mode. A blend mode defines how source pixels
      * (generated by a drawing command) are composited with the destination pixels
      * (content of the render target).
@@ -333,6 +461,15 @@
         return mPaint.getColor();
     }
 
+    /**
+     * Return the edge effect type to use.
+     *
+     * @return The edge effect type to use.
+     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
+     */
+    public @EdgeEffectType int getType() {
+        return mEdgeEffectType;
+    }
 
     /**
      * Returns the blend mode. A blend mode defines how source pixels
@@ -351,33 +488,59 @@
      * Draw into the provided canvas. Assumes that the canvas has been rotated
      * accordingly and the size has been set. The effect will be drawn the full
      * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
-     * 1.f of height.
+     * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a
+     * hardware canvas, e.g. {@link RenderNode#beginRecording()}.
      *
      * @param canvas Canvas to draw into
      * @return true if drawing should continue beyond this frame to continue the
      *         animation
      */
     public boolean draw(Canvas canvas) {
-        update();
+        if (mEdgeEffectType == TYPE_GLOW) {
+            update();
+            final int count = canvas.save();
 
-        final int count = canvas.save();
+            final float centerX = mBounds.centerX();
+            final float centerY = mBounds.height() - mRadius;
 
-        final float centerX = mBounds.centerX();
-        final float centerY = mBounds.height() - mRadius;
+            canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
 
-        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);
+            final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
+            float translateX = mBounds.width() * displacement / 2;
 
-        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
-        float translateX = mBounds.width() * displacement / 2;
+            canvas.clipRect(mBounds);
+            canvas.translate(translateX, 0);
+            mPaint.setAlpha((int) (0xff * mGlowAlpha));
+            canvas.drawCircle(centerX, centerY, mRadius, mPaint);
+            canvas.restoreToCount(count);
+        } else if (canvas instanceof RecordingCanvas) {
+            if (mState != STATE_PULL) {
+                update();
+            }
+            RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+            if (mTmpMatrix == null) {
+                mTmpMatrix = new Matrix();
+                mTmpPoints = new float[4];
+            }
+            //noinspection deprecation
+            recordingCanvas.getMatrix(mTmpMatrix);
+            mTmpPoints[0] = mBounds.width() * mDisplacement;
+            mTmpPoints[1] = mDistance * mBounds.height();
+            mTmpPoints[2] = mTmpPoints[0];
+            mTmpPoints[3] = 0;
+            mTmpMatrix.mapPoints(mTmpPoints);
+            float x = mTmpPoints[0] - mTmpPoints[2];
+            float y = mTmpPoints[1] - mTmpPoints[3];
 
-        canvas.clipRect(mBounds);
-        canvas.translate(translateX, 0);
-        mPaint.setAlpha((int) (0xff * mGlowAlpha));
-        canvas.drawCircle(centerX, centerY, mRadius, mPaint);
-        canvas.restoreToCount(count);
+            RenderNode renderNode = recordingCanvas.mNode;
+
+            // TODO: use stretchy RenderEffect and use internal API when it is ready
+            // TODO: wrap existing RenderEffect
+            renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y));
+        }
 
         boolean oneLastFrame = false;
-        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
+        if (mState == STATE_RECEDE && mDistance == 0) {
             mState = STATE_IDLE;
             oneLastFrame = true;
         }
@@ -402,6 +565,7 @@
 
         mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
         mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+        mDistance = mDistanceStart + (mDistanceFinish - mDistanceStart) * interp;
         mDisplacement = (mDisplacement + mTargetDisplacement) / 2;
 
         if (t >= 1.f - EPSILON) {
@@ -413,10 +577,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After absorb, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL:
                     mState = STATE_PULL_DECAY;
@@ -425,10 +591,12 @@
 
                     mGlowAlphaStart = mGlowAlpha;
                     mGlowScaleYStart = mGlowScaleY;
+                    mDistanceStart = mDistance;
 
                     // After pull, the glow should fade to nothing.
                     mGlowAlphaFinish = 0.f;
                     mGlowScaleYFinish = 0.f;
+                    mDistanceFinish = 0.f;
                     break;
                 case STATE_PULL_DECAY:
                     mState = STATE_RECEDE;
@@ -439,4 +607,20 @@
             }
         }
     }
+
+    /**
+     * @return The estimated pull distance as calculated from mGlowScaleY.
+     */
+    private float calculateDistanceFromGlowValues(float scale, float alpha) {
+        if (scale >= 1f) {
+            // It should asymptotically approach 1, but not reach there.
+            // Here, we're just choosing a value that is large.
+            return 1f;
+        }
+        if (scale > 0f) {
+            float v = 1f / 0.7f / (mGlowScaleY - 1f);
+            return v * v / mBounds.height();
+        }
+        return alpha / PULL_DISTANCE_ALPHA_GLOW_FACTOR;
+    }
 }
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 97dbb154..6dedd12 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -89,7 +89,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124053130)
-    private EdgeEffect mEdgeGlowLeft = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowLeft;
 
     /**
      * Tracks the state of the bottom edge glow.
@@ -98,7 +98,7 @@
      * setting it via reflection and they need to keep working until they target Q.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052619)
-    private EdgeEffect mEdgeGlowRight = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowRight;
 
     /**
      * Position of the last motion event.
@@ -186,6 +186,8 @@
     public HorizontalScrollView(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mEdgeGlowLeft = new EdgeEffect(context, attrs);
+        mEdgeGlowRight = new EdgeEffect(context, attrs);
         initScrollView();
 
         final TypedArray a = context.obtainStyledAttributes(
@@ -631,7 +633,15 @@
                 * otherwise don't.  mScroller.isFinished should be false when
                 * being flinged.
                 */
-                mIsBeingDragged = !mScroller.isFinished();
+                mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowLeft.isFinished()
+                        || !mEdgeGlowRight.isFinished();
+                // Catch the edge effect if it is active.
+                if (!mEdgeGlowLeft.isFinished()) {
+                    mEdgeGlowLeft.onPullDistance(0f, 1f - ev.getY() / getHeight());
+                }
+                if (!mEdgeGlowRight.isFinished()) {
+                    mEdgeGlowRight.onPullDistance(0f, ev.getY() / getHeight());
+                }
                 break;
             }
 
@@ -675,7 +685,8 @@
                 if (getChildCount() == 0) {
                     return false;
                 }
-                if ((mIsBeingDragged = !mScroller.isFinished())) {
+                if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowRight.isFinished()
+                        || !mEdgeGlowLeft.isFinished())) {
                     final ViewParent parent = getParent();
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
@@ -721,12 +732,26 @@
                     mLastMotionX = x;
 
                     final int oldX = mScrollX;
-                    final int oldY = mScrollY;
                     final int range = getScrollRange();
                     final int overscrollMode = getOverScrollMode();
                     final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
 
+                    final float displacement = ev.getY(activePointerIndex) / getHeight();
+                    if (canOverscroll) {
+                        int consumed = 0;
+                        if (deltaX < 0 && mEdgeGlowRight.getDistance() != 0f) {
+                            consumed = Math.round(getHeight()
+                                    * mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(),
+                                    displacement));
+                        } else if (deltaX > 0 && mEdgeGlowLeft.getDistance() != 0f) {
+                            consumed = Math.round(-getHeight()
+                                    * mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(),
+                                    1 - displacement));
+                        }
+                        deltaX -= consumed;
+                    }
+
                     // Calling overScrollBy will call onOverScrolled, which
                     // calls onScrollChanged if applicable.
                     if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0,
@@ -735,17 +760,17 @@
                         mVelocityTracker.clear();
                     }
 
-                    if (canOverscroll) {
+                    if (canOverscroll && deltaX != 0f) {
                         final int pulledToX = oldX + deltaX;
                         if (pulledToX < 0) {
-                            mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
-                                    1.f - ev.getY(activePointerIndex) / getHeight());
+                            mEdgeGlowLeft.onPullDistance((float) -deltaX / getWidth(),
+                                    1.f - displacement);
                             if (!mEdgeGlowRight.isFinished()) {
                                 mEdgeGlowRight.onRelease();
                             }
                         } else if (pulledToX > range) {
-                            mEdgeGlowRight.onPull((float) deltaX / getWidth(),
-                                    ev.getY(activePointerIndex) / getHeight());
+                            mEdgeGlowRight.onPullDistance((float) deltaX / getWidth(),
+                                    displacement);
                             if (!mEdgeGlowLeft.isFinished()) {
                                 mEdgeGlowLeft.onRelease();
                             }
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dfef7ca..4e3d99b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -47,6 +47,8 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Outline;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -79,6 +81,7 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.ViewManager;
+import android.view.ViewOutlineProvider;
 import android.view.ViewParent;
 import android.view.ViewStub;
 import android.widget.AdapterView.OnItemClickListener;
@@ -97,6 +100,8 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
@@ -169,6 +174,11 @@
      */
     private static final int MAX_NESTED_VIEWS = 10;
 
+    /**
+     * Maximum number of RemoteViews that can be specified in constructor.
+     */
+    private static final int MAX_INIT_VIEW_COUNT = 16;
+
     // The unique identifiers for each custom {@link Action}.
     private static final int SET_ON_CLICK_RESPONSE_TAG = 1;
     private static final int REFLECTION_ACTION_TAG = 2;
@@ -194,6 +204,7 @@
     private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
     private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26;
     private static final int SET_RADIO_GROUP_CHECKED = 27;
+    private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -287,7 +298,7 @@
      * The resource ID of the layout file. (Added to the parcel)
      */
     @UnsupportedAppUsage
-    private final int mLayoutId;
+    private int mLayoutId;
 
     /**
      * The resource ID of the layout file in dark text mode. (Added to the parcel)
@@ -319,6 +330,7 @@
      */
     private static final int MODE_NORMAL = 0;
     private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
+    private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2;
 
     /**
      * Used in conjunction with the special constructor
@@ -328,12 +340,26 @@
     private RemoteViews mLandscape = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private RemoteViews mPortrait = null;
+    /**
+     * List of RemoteViews with their ideal size. There must be at least two if the map is not null.
+     *
+     * The smallest remote view is always the last element in the list.
+     */
+    private List<RemoteViews> mSizedRemoteViews = null;
+
+    /**
+     * Ideal size for this RemoteViews.
+     *
+     * Only to be used on children views used in a {@link RemoteViews} with
+     * {@link RemoteViews#hasSizedRemoteViews()}.
+     */
+    private PointF mIdealSize = null;
 
     @ApplyFlags
     private int mApplyFlags = 0;
 
     /** Class cookies of the Parcel this instance was read from. */
-    private final Map<Class, Object> mClassCookies;
+    private Map<Class, Object> mClassCookies;
 
     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response)
             -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -2642,6 +2668,88 @@
         }
     }
 
+    private static class SetViewOutlinePreferredRadiusAction extends Action {
+
+        private final boolean mIsDimen;
+        private final int mValue;
+
+        SetViewOutlinePreferredRadiusAction(@IdRes int viewId, @DimenRes int dimenResId) {
+            this.viewId = viewId;
+            this.mIsDimen = true;
+            this.mValue = dimenResId;
+        }
+
+        SetViewOutlinePreferredRadiusAction(
+                @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+            this.viewId = viewId;
+            this.mIsDimen = false;
+            this.mValue = TypedValue.createComplexDimension(radius, units);
+
+        }
+
+        SetViewOutlinePreferredRadiusAction(Parcel in) {
+            viewId = in.readInt();
+            mIsDimen = in.readBoolean();
+            mValue = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+            dest.writeBoolean(mIsDimen);
+            dest.writeInt(mValue);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+                throws ActionException {
+            final View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            float radius;
+            if (mIsDimen) {
+                radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue);
+            } else {
+                radius = TypedValue.complexToDimensionPixelSize(mValue,
+                        target.getResources().getDisplayMetrics());
+            }
+            target.setOutlineProvider(new RemoteViewOutlineProvider(radius));
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_VIEW_OUTLINE_RADIUS_TAG;
+        }
+    }
+
+    /**
+     * OutlineProvider for a view with a radius set by
+     * {@link #setViewOutlinePreferredRadius(int, float, int)}.
+     */
+    public static final class RemoteViewOutlineProvider extends ViewOutlineProvider {
+
+        private final float mRadius;
+
+        public RemoteViewOutlineProvider(float radius) {
+            mRadius = radius;
+        }
+
+        /** Returns the corner radius used when providing the view outline. */
+        public float getRadius() {
+            return mRadius;
+        }
+
+        @Override
+        public void getOutline(@NonNull View view, @NonNull Outline outline) {
+            outline.setRoundRect(
+                    0 /*left*/,
+                    0 /* top */,
+                    view.getWidth() /* right */,
+                    view.getHeight() /* bottom */,
+                    mRadius);
+        }
+    }
+
     /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
@@ -2683,23 +2791,50 @@
         mClassCookies = null;
     }
 
+    private boolean hasMultipleLayouts() {
+        return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews();
+    }
+
     private boolean hasLandscapeAndPortraitLayouts() {
         return (mLandscape != null) && (mPortrait != null);
     }
 
+    private boolean hasSizedRemoteViews() {
+        return mSizedRemoteViews != null;
+    }
+
+    private @Nullable PointF getIdealSize() {
+        return mIdealSize;
+    }
+
+    private void setIdealSize(@Nullable PointF size) {
+        mIdealSize = size;
+    }
+
+    /**
+     * Finds the smallest view in {@code mSizedRemoteViews}.
+     * This method must not be called if {@code mSizedRemoteViews} is null.
+     */
+    private RemoteViews findSmallestRemoteView() {
+        return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1);
+    }
+
     /**
      * Create a new RemoteViews object that will inflate as the specified
      * landspace or portrait RemoteViews, depending on the current configuration.
      *
      * @param landscape The RemoteViews to inflate in landscape configuration
      * @param portrait The RemoteViews to inflate in portrait configuration
+     * @throws IllegalArgumentException if either landscape or portrait are null or if they are
+     *   not from the same application
      */
     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
         if (landscape == null || portrait == null) {
-            throw new RuntimeException("Both RemoteViews must be non-null");
+            throw new IllegalArgumentException("Both RemoteViews must be non-null");
         }
         if (!landscape.hasSameAppInfo(portrait.mApplication)) {
-            throw new RuntimeException("Both RemoteViews must share the same package and user");
+            throw new IllegalArgumentException(
+                    "Both RemoteViews must share the same package and user");
         }
         mApplication = portrait.mApplication;
         mLayoutId = portrait.mLayoutId;
@@ -2717,9 +2852,84 @@
     }
 
     /**
+     * Create a new RemoteViews object that will inflate the layout with the closest size
+     * specification.
+     *
+     * The default remote views in that case is always the smallest one provided.
+     *
+     * @param remoteViews Mapping of size to layout.
+     * @throws IllegalArgumentException if the map is empty, there are more than
+     *   MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application.
+     */
+    public RemoteViews(@NonNull Map<PointF, RemoteViews> remoteViews) {
+        if (remoteViews.isEmpty()) {
+            throw new IllegalArgumentException("The set of RemoteViews cannot be empty");
+        }
+        if (remoteViews.size() > MAX_INIT_VIEW_COUNT) {
+            throw new IllegalArgumentException("Too many RemoteViews in constructor");
+        }
+        if (remoteViews.size() == 1) {
+            initializeFrom(remoteViews.values().iterator().next());
+            return;
+        }
+        mBitmapCache = new BitmapCache();
+        mClassCookies = initializeSizedRemoteViews(
+                remoteViews.entrySet().stream().map(
+                        entry -> {
+                            entry.getValue().setIdealSize(entry.getKey());
+                            return entry.getValue();
+                        }
+                ).iterator()
+        );
+
+        RemoteViews smallestView = findSmallestRemoteView();
+        mApplication = smallestView.mApplication;
+        mLayoutId = smallestView.mLayoutId;
+        mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
+    }
+
+    // Initialize mSizedRemoteViews and return the class cookies.
+    private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) {
+        List<RemoteViews> sizedRemoteViews = new ArrayList<>();
+        Map<Class, Object> classCookies = null;
+        float viewArea = Float.MAX_VALUE;
+        RemoteViews smallestView = null;
+        while (remoteViews.hasNext()) {
+            RemoteViews view = remoteViews.next();
+            PointF size = view.getIdealSize();
+            float newViewArea = size.x * size.y;
+            if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) {
+                throw new IllegalArgumentException(
+                        "All RemoteViews must share the same package and user");
+            }
+            if (smallestView == null || newViewArea < viewArea) {
+                if (smallestView != null) {
+                    sizedRemoteViews.add(smallestView);
+                }
+                viewArea = newViewArea;
+                smallestView = view;
+            } else {
+                sizedRemoteViews.add(view);
+            }
+            configureRemoteViewsAsChild(view);
+            view.setIdealSize(size);
+            if (classCookies == null) {
+                classCookies = view.mClassCookies;
+            }
+        }
+        sizedRemoteViews.add(smallestView);
+        mSizedRemoteViews = sizedRemoteViews;
+        return classCookies;
+    }
+
+    /**
      * Creates a copy of another RemoteViews.
      */
     public RemoteViews(RemoteViews src) {
+        initializeFrom(src);
+    }
+
+    private void initializeFrom(RemoteViews src) {
         mBitmapCache = src.mBitmapCache;
         mApplication = src.mApplication;
         mIsRoot = src.mIsRoot;
@@ -2727,12 +2937,20 @@
         mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
         mApplyFlags = src.mApplyFlags;
         mClassCookies = src.mClassCookies;
+        mIdealSize = src.mIdealSize;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = new RemoteViews(src.mLandscape);
             mPortrait = new RemoteViews(src.mPortrait);
         }
 
+        if (src.hasSizedRemoteViews()) {
+            mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
+            for (RemoteViews srcView : src.mSizedRemoteViews) {
+                mSizedRemoteViews.add(new RemoteViews(srcView));
+            }
+        }
+
         if (src.mActions != null) {
             Parcel p = Parcel.obtain();
             p.putClassCookies(mClassCookies);
@@ -2782,10 +3000,29 @@
         if (mode == MODE_NORMAL) {
             mApplication = parcel.readInt() == 0 ? info :
                     ApplicationInfo.CREATOR.createFromParcel(parcel);
+            mIdealSize = parcel.readInt() == 0 ? null : PointF.CREATOR.createFromParcel(parcel);
             mLayoutId = parcel.readInt();
             mLightBackgroundLayoutId = parcel.readInt();
 
             readActionsFromParcel(parcel, depth);
+        } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) {
+            int numViews = parcel.readInt();
+            if (numViews > MAX_INIT_VIEW_COUNT) {
+                throw new IllegalArgumentException(
+                        "Too many views in mapping from size to RemoteViews.");
+            }
+            List<RemoteViews> remoteViews = new ArrayList<>(numViews);
+            for (int i = 0; i < numViews; i++) {
+                RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
+                        mClassCookies);
+                info = view.mApplication;
+                remoteViews.add(view);
+            }
+            initializeSizedRemoteViews(remoteViews.iterator());
+            RemoteViews smallestView = findSmallestRemoteView();
+            mApplication = smallestView.mApplication;
+            mLayoutId = smallestView.mLayoutId;
+            mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
         } else {
             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
             mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
@@ -2860,6 +3097,8 @@
                 return new SetCompoundButtonCheckedAction(parcel);
             case SET_RADIO_GROUP_CHECKED:
                 return new SetRadioGroupCheckedAction(parcel);
+            case SET_VIEW_OUTLINE_RADIUS_TAG:
+                return new SetViewOutlinePreferredRadiusAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -2903,16 +3142,20 @@
      */
     private void setBitmapCache(BitmapCache bitmapCache) {
         mBitmapCache = bitmapCache;
-        if (!hasLandscapeAndPortraitLayouts()) {
+        if (hasSizedRemoteViews()) {
+            for (RemoteViews remoteView : mSizedRemoteViews) {
+                remoteView.setBitmapCache(bitmapCache);
+            }
+        } else if (hasLandscapeAndPortraitLayouts()) {
+            mLandscape.setBitmapCache(bitmapCache);
+            mPortrait.setBitmapCache(bitmapCache);
+        } else {
             if (mActions != null) {
                 final int count = mActions.size();
-                for (int i= 0; i < count; ++i) {
+                for (int i = 0; i < count; ++i) {
                     mActions.get(i).setBitmapCache(bitmapCache);
                 }
             }
-        } else {
-            mLandscape.setBitmapCache(bitmapCache);
-            mPortrait.setBitmapCache(bitmapCache);
         }
     }
 
@@ -2931,10 +3174,10 @@
      * @param a The action to add
      */
     private void addAction(Action a) {
-        if (hasLandscapeAndPortraitLayouts()) {
-            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
-                    " layouts cannot be modified. Instead, fully configure the landscape and" +
-                    " portrait layouts individually before constructing the combined layout.");
+        if (hasMultipleLayouts()) {
+            throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
+                    + " or size cannot be modified. Instead, fully configure each layouts"
+                    + " individually before constructing the combined layout.");
         }
         if (mActions == null) {
             mActions = new ArrayList<>();
@@ -3595,6 +3838,28 @@
     }
 
     /**
+     * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using
+     * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. This outline may change shape
+     * during system transitions.
+     *
+     * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
+     * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
+     * display with a different density.
+     */
+    public void setViewOutlinePreferredRadius(
+            @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
+        addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units));
+    }
+
+    /**
+     * Sets an OutlineProvider on the view whose corner radius is a dimension resource with
+     * {@code resId}. This outline may change shape during system transitions.
+     */
+    public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) {
+        addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId));
+    }
+
+    /**
      * Call a method taking one boolean on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
@@ -3991,14 +4256,79 @@
             int orientation = context.getResources().getConfiguration().orientation;
             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                 return mLandscape;
-            } else {
-                return mPortrait;
             }
+            return mPortrait;
+        }
+        if (hasSizedRemoteViews()) {
+            return findSmallestRemoteView();
         }
         return this;
     }
 
     /**
+     * Returns the square distance between two points.
+     *
+     * This is particularly useful when we only care about the ordering of the distances.
+     */
+    private static float squareDistance(PointF p1, PointF p2) {
+        float dx = p1.x - p2.x;
+        float dy = p1.y - p2.y;
+        return dx * dx + dy * dy;
+    }
+
+    /**
+     * Returns whether the layout fits in the space available to the widget.
+     *
+     * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions
+     * are smaller than the ones of the widget, adding some padding to account for rounding errors.
+     */
+    private static boolean fitsIn(PointF sizeLayout, @Nullable PointF sizeWidget) {
+        return sizeWidget != null && (Math.ceil(sizeWidget.x) + 1 > sizeLayout.x)
+                && (Math.ceil(sizeWidget.y) + 1 > sizeLayout.y);
+    }
+
+    /**
+     * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the
+     * size of the widget.
+     *
+     * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is
+     * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the
+     * diagonal the most similar to the widget. If no layout fits or the size of the widget is
+     * not specified, the one with the smallest area will be chosen.
+     */
+    private RemoteViews getRemoteViewsToApply(@NonNull Context context,
+            @Nullable PointF widgetSize) {
+        if (!hasSizedRemoteViews()) {
+            // If there isn't multiple remote views, fall back on the previous methods.
+            return getRemoteViewsToApply(context);
+        }
+        // Find the better remote view
+        RemoteViews bestFit = null;
+        float bestSqDist = Float.MAX_VALUE;
+        for (RemoteViews layout : mSizedRemoteViews) {
+            PointF layoutSize = layout.getIdealSize();
+            if (fitsIn(layoutSize, widgetSize)) {
+                if (bestFit == null) {
+                    bestFit = layout;
+                    bestSqDist = squareDistance(layoutSize, widgetSize);
+                } else {
+                    float newSqDist = squareDistance(layoutSize, widgetSize);
+                    if (newSqDist < bestSqDist) {
+                        bestFit = layout;
+                        bestSqDist = newSqDist;
+                    }
+                }
+            }
+        }
+        if (bestFit == null) {
+            Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize);
+            return findSmallestRemoteView();
+        }
+        return bestFit;
+    }
+
+
+    /**
      * Inflates the view hierarchy represented by this object and applies
      * all of the actions.
      *
@@ -4015,7 +4345,13 @@
 
     /** @hide */
     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return apply(context, parent, handler, null);
+    }
+
+    /** @hide */
+    public View apply(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler, @Nullable PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         View result = inflateView(context, rvToApply, parent);
         rvToApply.performApply(result, parent, handler);
@@ -4023,9 +4359,17 @@
     }
 
     /** @hide */
-    public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler,
+    public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler,
             @StyleRes int applyThemeResId) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return applyWithTheme(context, parent, handler, applyThemeResId, null);
+    }
+
+    /** @hide */
+    public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
+            @Nullable OnClickHandler handler,
+            @StyleRes int applyThemeResId, @Nullable PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         View result = inflateView(context, rvToApply, parent, applyThemeResId);
         rvToApply.performApply(result, parent, handler);
@@ -4110,12 +4454,26 @@
     /** @hide */
     public CancellationSignal applyAsync(Context context, ViewGroup parent,
             Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
-        return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
+        return applyAsync(context, parent, executor, listener, handler, null);
+    }
+
+    /** @hide */
+    public CancellationSignal applyAsync(Context context, ViewGroup parent,
+            Executor executor, OnViewAppliedListener listener, OnClickHandler handler,
+            PointF size) {
+        return getAsyncApplyTask(context, parent, listener, handler, size).startTaskOnExecutor(
+                executor);
     }
 
     private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
             OnViewAppliedListener listener, OnClickHandler handler) {
-        return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
+        return getAsyncApplyTask(context, parent, listener, handler, null);
+    }
+
+    private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
+            OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+        return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context,
+                listener,
                 handler, null);
     }
 
@@ -4232,12 +4590,18 @@
 
     /** @hide */
     public void reapply(Context context, View v, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        reapply(context, v, handler, null);
+    }
 
-        // In the case that a view has this RemoteViews applied in one orientation, is persisted
-        // across orientation change, and has the RemoteViews re-applied in the new orientation,
-        // we throw an exception, since the layouts may be completely unrelated.
-        if (hasLandscapeAndPortraitLayouts()) {
+    /** @hide */
+    public void reapply(Context context, View v, OnClickHandler handler, PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
+
+        // In the case that a view has this RemoteViews applied in one orientation or size, is
+        // persisted across change, and has the RemoteViews re-applied in a different situation
+        // (orientation or size), we throw an exception, since the layouts may be completely
+        // unrelated.
+        if (hasMultipleLayouts()) {
             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
@@ -4268,12 +4632,18 @@
     /** @hide */
     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
             OnViewAppliedListener listener, OnClickHandler handler) {
-        RemoteViews rvToApply = getRemoteViewsToApply(context);
+        return reapplyAsync(context, v, executor, listener, handler, null);
+    }
+
+    /** @hide */
+    public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
+            OnViewAppliedListener listener, OnClickHandler handler, PointF size) {
+        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
 
         // In the case that a view has this RemoteViews applied in one orientation, is persisted
         // across orientation change, and has the RemoteViews re-applied in the new orientation,
         // we throw an exception, since the layouts may be completely unrelated.
-        if (hasLandscapeAndPortraitLayouts()) {
+        if (hasMultipleLayouts()) {
             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                         " that does not share the same root layout id.");
@@ -4357,7 +4727,7 @@
     }
 
     public void writeToParcel(Parcel dest, int flags) {
-        if (!hasLandscapeAndPortraitLayouts()) {
+        if (!hasMultipleLayouts()) {
             dest.writeInt(MODE_NORMAL);
             // We only write the bitmap cache if we are the root RemoteViews, as this cache
             // is shared by all children.
@@ -4370,9 +4740,26 @@
                 dest.writeInt(1);
                 mApplication.writeToParcel(dest, flags);
             }
+            if (mIsRoot || mIdealSize == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                mIdealSize.writeToParcel(dest, flags);
+            }
             dest.writeInt(mLayoutId);
             dest.writeInt(mLightBackgroundLayoutId);
             writeActionsToParcel(dest);
+        } else if (hasSizedRemoteViews()) {
+            dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
+            if (mIsRoot) {
+                mBitmapCache.writeBitmapsToParcel(dest, flags);
+            }
+            int childFlags = flags;
+            dest.writeInt(mSizedRemoteViews.size());
+            for (RemoteViews view : mSizedRemoteViews) {
+                view.writeToParcel(dest, childFlags);
+                childFlags |= PARCELABLE_ELIDE_DUPLICATES;
+            }
         } else {
             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
             // We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -4626,11 +5013,9 @@
          * before starting the intent.
          *
          * @param fillIntent The intent which will be combined with the parent's PendingIntent in
-         *                  order to determine the behavior of the response
-         *
+         *                   order to determine the behavior of the response
          * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent)
          * @see RemoteViews#setOnClickFillInIntent(int, Intent)
-         * @return
          */
         @NonNull
         public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
@@ -4645,9 +5030,8 @@
          * the epicenter for the exit Transition. The position of the associated shared element in
          * the launched Activity will be the epicenter of its entering Transition.
          *
-         * @param viewId The id of the view to be shared as part of the transition
+         * @param viewId            The id of the view to be shared as part of the transition
          * @param sharedElementName The shared element name for this view
-         *
          * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
          */
         @NonNull
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index f3de982..64d09de 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -98,7 +98,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600)
-    private EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowTop;
 
     /**
      * Tracks the state of the bottom edge glow.
@@ -108,7 +108,7 @@
      */
     @NonNull
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386)
-    private EdgeEffect mEdgeGlowBottom = new EdgeEffect(getContext());
+    private EdgeEffect mEdgeGlowBottom;
 
     /**
      * Position of the last motion event.
@@ -213,6 +213,8 @@
 
     public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mEdgeGlowTop = new EdgeEffect(context, attrs);
+        mEdgeGlowBottom = new EdgeEffect(context, attrs);
         initScrollView();
 
         final TypedArray a = context.obtainStyledAttributes(
@@ -679,7 +681,15 @@
                  * isFinished() is correct.
                 */
                 mScroller.computeScrollOffset();
-                mIsBeingDragged = !mScroller.isFinished();
+                mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
+                    || !mEdgeGlowTop.isFinished();
+                // Catch the edge effect if it is active.
+                if (!mEdgeGlowTop.isFinished()) {
+                    mEdgeGlowTop.onPullDistance(0f, ev.getX() / getWidth());
+                }
+                if (!mEdgeGlowBottom.isFinished()) {
+                    mEdgeGlowBottom.onPullDistance(0f, 1f - ev.getX() / getWidth());
+                }
                 if (mIsBeingDragged && mScrollStrictSpan == null) {
                     mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                 }
@@ -732,7 +742,8 @@
                 if (getChildCount() == 0) {
                     return false;
                 }
-                if ((mIsBeingDragged = !mScroller.isFinished())) {
+                if ((mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowTop.isFinished()
+                        || !mEdgeGlowBottom.isFinished())) {
                     final ViewParent parent = getParent();
                     if (parent != null) {
                         parent.requestDisallowInterceptTouchEvent(true);
@@ -793,6 +804,21 @@
                     boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
 
+                    final float displacement = ev.getX(activePointerIndex) / getWidth();
+                    if (canOverscroll) {
+                        int consumed = 0;
+                        if (deltaY < 0 && mEdgeGlowBottom.getDistance() != 0f) {
+                            consumed = Math.round(getHeight()
+                                    * mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+                                    1 - displacement));
+                        } else if (deltaY > 0 && mEdgeGlowTop.getDistance() != 0f) {
+                            consumed = Math.round(-getHeight()
+                                    * mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+                                    displacement));
+                        }
+                        deltaY -= consumed;
+                    }
+
                     // Calling overScrollBy will call onOverScrolled, which
                     // calls onScrollChanged if applicable.
                     if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
@@ -807,17 +833,17 @@
                         mLastMotionY -= mScrollOffset[1];
                         vtev.offsetLocation(0, mScrollOffset[1]);
                         mNestedYOffset += mScrollOffset[1];
-                    } else if (canOverscroll) {
+                    } else if (canOverscroll && deltaY != 0f) {
                         final int pulledToY = oldY + deltaY;
                         if (pulledToY < 0) {
-                            mEdgeGlowTop.onPull((float) deltaY / getHeight(),
-                                    ev.getX(activePointerIndex) / getWidth());
+                            mEdgeGlowTop.onPullDistance((float) -deltaY / getHeight(),
+                                    displacement);
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
                         } else if (pulledToY > range) {
-                            mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
-                                    1.f - ev.getX(activePointerIndex) / getWidth());
+                            mEdgeGlowBottom.onPullDistance((float) deltaY / getHeight(),
+                                    1.f - displacement);
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 794b642..d59a415 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -69,9 +69,7 @@
     private final TextView mTextView;
 
     SpellCheckerSession mSpellCheckerSession;
-    // We assume that the sentence level spell check will always provide better results than words.
-    // Although word SC has a sequential option.
-    private boolean mIsSentenceSpellCheckSupported;
+
     final int mCookie;
 
     // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -134,7 +132,6 @@
                             | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
                             | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
                             | SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS);
-            mIsSentenceSpellCheckSupported = true;
         }
 
         // Restore SpellCheckSpans in pool
@@ -318,13 +315,11 @@
                     && WordIterator.isMidWordPunctuation(
                             mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
                 isEditing = false;
-            } else if (mIsSentenceSpellCheckSupported) {
+            } else {
                 // Allow the overlap of the cursor and the first boundary of the spell check span
                 // no to skip the spell check of the following word because the
                 // following word will never be spell-checked even if the user finishes composing
                 isEditing = selectionEnd <= start || selectionStart > end;
-            } else {
-                isEditing = selectionEnd < start || selectionStart > end;
             }
             if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
                 spellCheckSpan.setSpellCheckInProgress(true);
@@ -346,13 +341,8 @@
                 textInfos = textInfosCopy;
             }
 
-            if (mIsSentenceSpellCheckSupported) {
-                mSpellCheckerSession.getSentenceSuggestions(
-                        textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
-            } else {
-                mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
-                        false /* TODO Set sequentialWords to true for initial spell check */);
-            }
+            mSpellCheckerSession.getSentenceSuggestions(
+                    textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
         }
     }
 
@@ -381,32 +371,30 @@
                             editable, suggestionsInfo, spellCheckSpan, offset, length);
                 } else {
                     // Valid word -- isInDictionary || !looksLikeTypo
-                    if (mIsSentenceSpellCheckSupported) {
-                        // Allow the spell checker to remove existing misspelled span by
-                        // overwriting the span over the same place
-                        final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
-                        final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
-                        final int start;
-                        final int end;
-                        if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
-                            start = spellCheckSpanStart + offset;
-                            end = start + length;
-                        } else {
-                            start = spellCheckSpanStart;
-                            end = spellCheckSpanEnd;
-                        }
-                        if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
-                                && end > start) {
-                            final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
-                            final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
-                            if (tempSuggestionSpan != null) {
-                                if (DBG) {
-                                    Log.i(TAG, "Remove existing misspelled span. "
-                                            + editable.subSequence(start, end));
-                                }
-                                editable.removeSpan(tempSuggestionSpan);
-                                mSuggestionSpanCache.remove(key);
+                    // Allow the spell checker to remove existing misspelled span by
+                    // overwriting the span over the same place
+                    final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
+                    final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
+                    final int start;
+                    final int end;
+                    if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
+                        start = spellCheckSpanStart + offset;
+                        end = start + length;
+                    } else {
+                        start = spellCheckSpanStart;
+                        end = spellCheckSpanEnd;
+                    }
+                    if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
+                            && end > start) {
+                        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+                        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+                        if (tempSuggestionSpan != null) {
+                            if (DBG) {
+                                Log.i(TAG, "Remove existing misspelled span. "
+                                        + editable.subSequence(start, end));
                             }
+                            editable.removeSpan(tempSuggestionSpan);
+                            mSuggestionSpanCache.remove(key);
                         }
                     }
                 }
@@ -531,20 +519,16 @@
         }
         SuggestionSpan suggestionSpan =
                 new SuggestionSpan(mTextView.getContext(), suggestions, flags);
-        // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
-        // to share the logic of word level spell checker and sentence level spell checker
-        if (mIsSentenceSpellCheckSupported) {
-            final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
-            final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
-            if (tempSuggestionSpan != null) {
-                if (DBG) {
-                    Log.i(TAG, "Cached span on the same position is cleard. "
-                            + editable.subSequence(start, end));
-                }
-                editable.removeSpan(tempSuggestionSpan);
+        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
+        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
+        if (tempSuggestionSpan != null) {
+            if (DBG) {
+                Log.i(TAG, "Cached span on the same position is cleard. "
+                        + editable.subSequence(start, end));
             }
-            mSuggestionSpanCache.put(key, suggestionSpan);
+            editable.removeSpan(tempSuggestionSpan);
         }
+        mSuggestionSpanCache.put(key, suggestionSpan);
         editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         mTextView.invalidateRegion(start, end, false /* No cursor involved */);
@@ -599,15 +583,8 @@
         public void parse() {
             Editable editable = (Editable) mTextView.getText();
             // Iterate over the newly added text and schedule new SpellCheckSpans
-            final int start;
-            if (mIsSentenceSpellCheckSupported) {
-                // TODO: Find the start position of the sentence.
-                // Set span with the context
-                start =  Math.max(
-                        0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
-            } else {
-                start = editable.getSpanStart(mRange);
-            }
+            final int start =  Math.max(
+                    0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
 
             final int end = editable.getSpanEnd(mRange);
 
@@ -633,155 +610,80 @@
                 return;
             }
 
-            // We need to expand by one character because we want to include the spans that
-            // end/start at position start/end respectively.
-            SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1,
-                    SpellCheckSpan.class);
-            SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
-                    SuggestionSpan.class);
-
-            int wordCount = 0;
             boolean scheduleOtherSpellCheck = false;
 
-            if (mIsSentenceSpellCheckSupported) {
-                if (wordIteratorWindowEnd < end) {
-                    if (DBG) {
-                        Log.i(TAG, "schedule other spell check.");
-                    }
-                    // Several batches needed on that region. Cut after last previous word
-                    scheduleOtherSpellCheck = true;
+            if (wordIteratorWindowEnd < end) {
+                if (DBG) {
+                    Log.i(TAG, "schedule other spell check.");
                 }
-                int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
-                boolean correct = spellCheckEnd != BreakIterator.DONE;
-                if (correct) {
-                    spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
-                    correct = spellCheckEnd != BreakIterator.DONE;
-                }
-                if (!correct) {
-                    if (DBG) {
-                        Log.i(TAG, "Incorrect range span.");
-                    }
-                    stop();
-                    return;
-                }
-                do {
-                    // TODO: Find the start position of the sentence.
-                    int spellCheckStart = wordStart;
-                    boolean createSpellCheckSpan = true;
-                    // Cancel or merge overlapped spell check spans
-                    for (int i = 0; i < mLength; ++i) {
-                        final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
-                        if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
-                            continue;
-                        }
-                        final int spanStart = editable.getSpanStart(spellCheckSpan);
-                        final int spanEnd = editable.getSpanEnd(spellCheckSpan);
-                        if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
-                            // No need to merge
-                            continue;
-                        }
-                        if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
-                            // There is a completely overlapped spell check span
-                            // skip this span
-                            createSpellCheckSpan = false;
-                            if (DBG) {
-                                Log.i(TAG, "The range is overrapped. Skip spell check.");
-                            }
-                            break;
-                        }
-                        // This spellCheckSpan is replaced by the one we are creating
-                        editable.removeSpan(spellCheckSpan);
-                        spellCheckStart = Math.min(spanStart, spellCheckStart);
-                        spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
-                    }
-
-                    if (DBG) {
-                        Log.d(TAG, "addSpellCheckSpan: "
-                                + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
-                                + ", next = " + scheduleOtherSpellCheck + "\n"
-                                + editable.subSequence(spellCheckStart, spellCheckEnd));
-                    }
-
-                    // Stop spell checking when there are no characters in the range.
-                    if (spellCheckEnd < start) {
-                        break;
-                    }
-                    if (spellCheckEnd <= spellCheckStart) {
-                        Log.w(TAG, "Trying to spellcheck invalid region, from "
-                                + start + " to " + end);
-                        break;
-                    }
-                    if (createSpellCheckSpan) {
-                        addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
-                    }
-                } while (false);
-                wordStart = spellCheckEnd;
-            } else {
-                while (wordStart <= end) {
-                    if (wordEnd >= start && wordEnd > wordStart) {
-                        if (wordCount >= MAX_NUMBER_OF_WORDS) {
-                            scheduleOtherSpellCheck = true;
-                            break;
-                        }
-                        // A new word has been created across the interval boundaries with this
-                        // edit. The previous spans (that ended on start / started on end) are
-                        // not valid anymore and must be removed.
-                        if (wordStart < start && wordEnd > start) {
-                            removeSpansAt(editable, start, spellCheckSpans);
-                            removeSpansAt(editable, start, suggestionSpans);
-                        }
-
-                        if (wordStart < end && wordEnd > end) {
-                            removeSpansAt(editable, end, spellCheckSpans);
-                            removeSpansAt(editable, end, suggestionSpans);
-                        }
-
-                        // Do not create new boundary spans if they already exist
-                        boolean createSpellCheckSpan = true;
-                        if (wordEnd == start) {
-                            for (int i = 0; i < spellCheckSpans.length; i++) {
-                                final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
-                                if (spanEnd == start) {
-                                    createSpellCheckSpan = false;
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (wordStart == end) {
-                            for (int i = 0; i < spellCheckSpans.length; i++) {
-                                final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
-                                if (spanStart == end) {
-                                    createSpellCheckSpan = false;
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (createSpellCheckSpan) {
-                            addSpellCheckSpan(editable, wordStart, wordEnd);
-                        }
-                        wordCount++;
-                    }
-
-                    // iterate word by word
-                    int originalWordEnd = wordEnd;
-                    wordEnd = mWordIterator.following(wordEnd);
-                    if ((wordIteratorWindowEnd < end) &&
-                            (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
-                        wordIteratorWindowEnd =
-                                Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
-                        mWordIterator.setCharSequence(
-                                editable, originalWordEnd, wordIteratorWindowEnd);
-                        wordEnd = mWordIterator.following(originalWordEnd);
-                    }
-                    if (wordEnd == BreakIterator.DONE) break;
-                    wordStart = mWordIterator.getBeginning(wordEnd);
-                    if (wordStart == BreakIterator.DONE) {
-                        break;
-                    }
-                }
+                // Several batches needed on that region. Cut after last previous word
+                scheduleOtherSpellCheck = true;
             }
+            int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
+            boolean correct = spellCheckEnd != BreakIterator.DONE;
+            if (correct) {
+                spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
+                correct = spellCheckEnd != BreakIterator.DONE;
+            }
+            if (!correct) {
+                if (DBG) {
+                    Log.i(TAG, "Incorrect range span.");
+                }
+                stop();
+                return;
+            }
+            do {
+                // TODO: Find the start position of the sentence.
+                int spellCheckStart = wordStart;
+                boolean createSpellCheckSpan = true;
+                // Cancel or merge overlapped spell check spans
+                for (int i = 0; i < mLength; ++i) {
+                    final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
+                    if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
+                        continue;
+                    }
+                    final int spanStart = editable.getSpanStart(spellCheckSpan);
+                    final int spanEnd = editable.getSpanEnd(spellCheckSpan);
+                    if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
+                        // No need to merge
+                        continue;
+                    }
+                    if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
+                        // There is a completely overlapped spell check span
+                        // skip this span
+                        createSpellCheckSpan = false;
+                        if (DBG) {
+                            Log.i(TAG, "The range is overrapped. Skip spell check.");
+                        }
+                        break;
+                    }
+                    // This spellCheckSpan is replaced by the one we are creating
+                    editable.removeSpan(spellCheckSpan);
+                    spellCheckStart = Math.min(spanStart, spellCheckStart);
+                    spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
+                }
+
+                if (DBG) {
+                    Log.d(TAG, "addSpellCheckSpan: "
+                            + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
+                            + ", next = " + scheduleOtherSpellCheck + "\n"
+                            + editable.subSequence(spellCheckStart, spellCheckEnd));
+                }
+
+                // Stop spell checking when there are no characters in the range.
+                if (spellCheckEnd < start) {
+                    break;
+                }
+                if (spellCheckEnd <= spellCheckStart) {
+                    Log.w(TAG, "Trying to spellcheck invalid region, from "
+                            + start + " to " + end);
+                    break;
+                }
+                if (createSpellCheckSpan) {
+                    addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
+                }
+            } while (false);
+            wordStart = spellCheckEnd;
 
             if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
                 // Update range span: start new spell check from last wordStart
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fe37c53..0f2089a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12940,17 +12940,11 @@
             return false;
         }
 
-        final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
-        final ClipDescription description = clipData.getDescription();
+        final ClipDescription description =
+                getClipboardManagerForUser().getPrimaryClipDescription();
         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
-        final CharSequence text = clipData.getItemAt(0).getText();
-        if (isPlainType && (text instanceof Spanned)) {
-            Spanned spanned = (Spanned) text;
-            if (TextUtils.hasStyleSpan(spanned)) {
-                return true;
-            }
-        }
-        return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
+        return (isPlainType && description.isStyledText())
+                || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
     }
 
     boolean canProcessText() {
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index b484dfa..2904a8c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -172,6 +172,22 @@
     }
 
     /**
+     * Update the LayoutParameters of the currently showing toast view. This is used for layout
+     * updates based on orientation changes.
+     */
+    public void updateLayoutParams(int xOffset, int yOffset, float horizontalMargin,
+            float verticalMargin, int gravity) {
+        checkState(mView != null, "Toast must be showing to update its layout parameters.");
+        Configuration config = mResources.getConfiguration();
+        mParams.gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
+        mParams.x = xOffset;
+        mParams.y = yOffset;
+        mParams.horizontalMargin = horizontalMargin;
+        mParams.verticalMargin = verticalMargin;
+        addToastView();
+    }
+
+    /**
      * Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
      * packageName} is a cross-user package.
      *
@@ -221,18 +237,7 @@
 
         adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                 horizontalMargin, verticalMargin, removeWindowAnimations);
-        if (mView.getParent() != null) {
-            mWindowManager.removeView(mView);
-        }
-        try {
-            mWindowManager.addView(mView, mParams);
-        } catch (WindowManager.BadTokenException e) {
-            // Since the notification manager service cancels the token right after it notifies us
-            // to cancel the toast there is an inherent race and we may attempt to add a window
-            // after the token has been invalidated. Let us hedge against that.
-            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
-            return;
-        }
+        addToastView();
         trySendAccessibilityEvent(mView, mPackageName);
         if (callback != null) {
             try {
@@ -288,4 +293,19 @@
         view.dispatchPopulateAccessibilityEvent(event);
         mAccessibilityManager.sendAccessibilityEvent(event);
     }
+
+    private void addToastView() {
+        if (mView.getParent() != null) {
+            mWindowManager.removeView(mView);
+        }
+        try {
+            mWindowManager.addView(mView, mParams);
+        } catch (WindowManager.BadTokenException e) {
+            // Since the notification manager service cancels the token right after it notifies us
+            // to cancel the toast there is an inherent race and we may attempt to add a window
+            // after the token has been invalidated. Let us hedge against that.
+            Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+            return;
+        }
+    }
 }
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 88b2257..8f541d0 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -42,6 +42,11 @@
     void removeStartingWindow(int taskId);
 
     /**
+     * Called when the Task want to copy the splash screen.
+     */
+    void copySplashScreenView(int taskId);
+
+    /**
      * A callback when the Task is available for the registered organizer. The client is responsible
      * for releasing the SurfaceControl in the callback. For non-root tasks, the leash may initially
      * be hidden so it is up to the organizer to show this task.
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
new file mode 100644
index 0000000..4b88a9b
--- /dev/null
+++ b/core/java/android/window/SplashScreen.java
@@ -0,0 +1,188 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.Singleton;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * The interface that apps use to talk to the splash screen.
+ * <p>
+ * Each splash screen instance is bound to a particular {@link Activity}.
+ * To obtain a {@link SplashScreen} for an Activity, use
+ * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p>
+ */
+public interface SplashScreen {
+    /**
+     * <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its
+     * own. Normally the splash screen will show on screen before the content of the activity has
+     * been drawn, and disappear when the activity is showing on the screen. With this listener set,
+     * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if
+     * splash screen is showed, then the activity can create its own exit animation based on the
+     * SplashScreenView.</p>
+     *
+     * <p> Note that this method must be called before splash screen leave, so it only takes effect
+     * during or before {@link Activity#onResume}.</p>
+     *
+     * @param listener the listener for receive the splash screen with
+     *
+     * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView)
+     */
+    @SuppressLint("ExecutorRegistration")
+    void setOnExitAnimationListener(@Nullable SplashScreen.OnExitAnimationListener listener);
+
+    /**
+     * Listens for the splash screen exit event.
+     */
+    interface OnExitAnimationListener {
+        /**
+         * When receiving this callback, the {@link SplashScreenView} object will be drawing on top
+         * of the activity. The {@link SplashScreenView} represents the splash screen view
+         * object, developer can make an exit animation based on this view.</p>
+         *
+         * <p>If {@link SplashScreenView#remove} is not called after 5000ms, the method will be
+         * automatically called and the splash screen removed.</p>
+         *
+         * <p>This method is never invoked if your activity sets
+         * {@link #setOnExitAnimationListener} to <code>null</code>..
+         *
+         * @param view The view object which on top of this Activity.
+         * @see #setOnExitAnimationListener
+         */
+        void onSplashScreenExit(@NonNull SplashScreenView view);
+    }
+
+    /**
+     * @hide
+     */
+    class SplashScreenImpl implements SplashScreen {
+        private OnExitAnimationListener mExitAnimationListener;
+        private final IBinder mActivityToken;
+        private final SplashScreenManagerGlobal mGlobal;
+
+        public SplashScreenImpl(Context context) {
+            mActivityToken = context.getActivityToken();
+            mGlobal = SplashScreenManagerGlobal.getInstance();
+        }
+
+        @Override
+        public void setOnExitAnimationListener(
+                @Nullable SplashScreen.OnExitAnimationListener listener) {
+            if (mActivityToken == null) {
+                // This is not an activity.
+                return;
+            }
+            synchronized (mGlobal.mGlobalLock) {
+                mExitAnimationListener = listener;
+                if (listener != null) {
+                    mGlobal.addImpl(this);
+                } else {
+                    mGlobal.removeImpl(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * This class is only used internally to manage the activities for this process.
+     *
+     * @hide
+     */
+    class SplashScreenManagerGlobal {
+        private static final String TAG = SplashScreen.class.getSimpleName();
+        private final Object mGlobalLock = new Object();
+        private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();
+
+        private SplashScreenManagerGlobal() {
+            ActivityThread.currentActivityThread().registerSplashScreenManager(this);
+        }
+
+        public static SplashScreenManagerGlobal getInstance() {
+            return sInstance.get();
+        }
+
+        private static final Singleton<SplashScreenManagerGlobal> sInstance =
+                new Singleton<SplashScreenManagerGlobal>() {
+                    @Override
+                    protected SplashScreenManagerGlobal create() {
+                        return new SplashScreenManagerGlobal();
+                    }
+                };
+
+        private void addImpl(SplashScreenImpl impl) {
+            synchronized (mGlobalLock) {
+                mImpls.add(impl);
+            }
+        }
+
+        private void removeImpl(SplashScreenImpl impl) {
+            synchronized (mGlobalLock) {
+                mImpls.remove(impl);
+            }
+        }
+
+        private SplashScreenImpl findImpl(IBinder token) {
+            synchronized (mGlobalLock) {
+                for (SplashScreenImpl impl : mImpls) {
+                    if (impl.mActivityToken == token) {
+                        return impl;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public void tokenDestroyed(IBinder token) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                if (impl != null) {
+                    removeImpl(impl);
+                }
+            }
+        }
+
+        public void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                if (impl == null) {
+                    return;
+                }
+                if (impl.mExitAnimationListener == null) {
+                    Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token);
+                    return;
+                }
+                impl.mExitAnimationListener.onSplashScreenExit(view);
+            }
+        }
+
+        public boolean containsExitListener(IBinder token) {
+            synchronized (mGlobalLock) {
+                final SplashScreenImpl impl = findImpl(token);
+                return impl != null && impl.mExitAnimationListener != null;
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/android/window/SplashScreenView.aidl
similarity index 78%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/android/window/SplashScreenView.aidl
index 14d57bf..cc7ac1e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/android/window/SplashScreenView.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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,6 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.window;
 
-parcelable ExternalTimeSuggestion;
+/** @hide */
+parcelable SplashScreenView.SplashScreenViewParcelable;
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
new file mode 100644
index 0000000..35ccfca
--- /dev/null
+++ b/core/java/android/window/SplashScreenView.java
@@ -0,0 +1,510 @@
+/*
+ * 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.window;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.policy.DecorView;
+
+/**
+ * <p>The view which allows an activity to customize its splash screen exit animation.</p>
+ *
+ * <p>Activities will receive this view as a parameter of
+ * {@link SplashScreen.OnExitAnimationListener#onSplashScreenExit} if
+ * they set {@link SplashScreen#setOnExitAnimationListener}.
+ * When this callback is called, this view will be on top of the activity.</p>
+ *
+ * <p>This view is composed of a view containing the splashscreen icon (see
+ * windowSplashscreenAnimatedIcon) and a background.
+ * Developers can use {@link #getIconView} to get this view and replace the drawable or
+ * add animation to it. The background of this view is filled with a single color, which can be
+ * edited during the animation by {@link View#setBackground} or {@link View#setBackgroundColor}.</p>
+ *
+ * @see SplashScreen
+ */
+public final class SplashScreenView extends FrameLayout {
+    private static final String TAG = SplashScreenView.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private boolean mNotCopyable;
+    private int mInitBackgroundColor;
+    private View mIconView;
+    private Bitmap mParceledIconBitmap;
+    private View mBrandingImageView;
+    private Bitmap mParceledBrandingBitmap;
+    private long mIconAnimationDuration;
+    private long mIconAnimationStart;
+
+    private Animatable mAnimatableIcon;
+    private ValueAnimator mAnimator;
+
+    // cache original window and status
+    private Window mWindow;
+    private boolean mDrawBarBackground;
+    private int mStatusBarColor;
+    private int mNavigationBarColor;
+
+    /**
+     * Internal builder to create a SplashScreenWindowView object.
+     * @hide
+     */
+    public static class Builder {
+        private final Context mContext;
+        private int mIconSize;
+        private @ColorInt int mBackgroundColor;
+        private Bitmap mParceledIconBitmap;
+        private Drawable mIconDrawable;
+        private int mBrandingImageWidth;
+        private int mBrandingImageHeight;
+        private Drawable mBrandingDrawable;
+        private Bitmap mParceledBrandingBitmap;
+        private long mIconAnimationStart;
+        private long mIconAnimationDuration;
+
+        public Builder(@NonNull Context context) {
+            mContext = context;
+        }
+
+        /**
+         * When create from {@link SplashScreenViewParcelable}, all the materials were be settled so
+         * you do not need to call other set methods.
+         */
+        public Builder createFromParcel(SplashScreenViewParcelable parcelable) {
+            mIconSize = parcelable.getIconSize();
+            mBackgroundColor = parcelable.getBackgroundColor();
+            if (parcelable.mIconBitmap != null) {
+                mIconDrawable = new BitmapDrawable(mContext.getResources(), parcelable.mIconBitmap);
+                mParceledIconBitmap = parcelable.mIconBitmap;
+            }
+            if (parcelable.mBrandingBitmap != null) {
+                setBrandingDrawable(new BitmapDrawable(mContext.getResources(),
+                                parcelable.mBrandingBitmap), parcelable.mBrandingWidth,
+                        parcelable.mBrandingHeight);
+                mParceledBrandingBitmap = parcelable.mBrandingBitmap;
+            }
+            mIconAnimationStart = parcelable.mIconAnimationStart;
+            mIconAnimationDuration = parcelable.mIconAnimationDuration;
+            return this;
+        }
+
+        /**
+         * Set the rectangle size for the center view.
+         */
+        public Builder setIconSize(int iconSize) {
+            mIconSize = iconSize;
+            return this;
+        }
+
+        /**
+         * Set the background color for the view.
+         */
+        public Builder setBackgroundColor(@ColorInt int backgroundColor) {
+            mBackgroundColor = backgroundColor;
+            return this;
+        }
+
+        /**
+         * Set the Drawable object to fill the center view.
+         */
+        public Builder setCenterViewDrawable(Drawable drawable) {
+            mIconDrawable = drawable;
+            return this;
+        }
+
+        /**
+         * Set the animation duration if icon is animatable.
+         */
+        public Builder setAnimationDuration(int duration) {
+            mIconAnimationDuration = duration;
+            return this;
+        }
+
+        /**
+         * Set the Drawable object and size for the branding view.
+         */
+        public Builder setBrandingDrawable(Drawable branding, int width, int height) {
+            mBrandingDrawable = branding;
+            mBrandingImageWidth = width;
+            mBrandingImageHeight = height;
+            return this;
+        }
+
+        /**
+         * Create SplashScreenWindowView object from materials.
+         */
+        public SplashScreenView build() {
+            final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+            final SplashScreenView view = (SplashScreenView)
+                    layoutInflater.inflate(R.layout.splash_screen_view, null, false);
+            view.mInitBackgroundColor = mBackgroundColor;
+            view.setBackgroundColor(mBackgroundColor);
+            view.mIconView = view.findViewById(R.id.splashscreen_icon_view);
+            view.mBrandingImageView = view.findViewById(R.id.splashscreen_branding_view);
+            // center icon
+            if (mIconSize != 0) {
+                final ViewGroup.LayoutParams params = view.mIconView.getLayoutParams();
+                params.width = mIconSize;
+                params.height = mIconSize;
+                view.mIconView.setLayoutParams(params);
+            }
+            if (mIconDrawable != null) {
+                view.mIconView.setBackground(mIconDrawable);
+                view.initIconAnimation(mIconDrawable, mIconAnimationDuration);
+            }
+            view.mIconAnimationStart = mIconAnimationStart;
+            view.mIconAnimationDuration = mIconAnimationDuration;
+            if (mParceledIconBitmap != null) {
+                view.mParceledIconBitmap = mParceledIconBitmap;
+            }
+            // branding image
+            if (mBrandingImageHeight > 0 && mBrandingImageWidth > 0) {
+                final ViewGroup.LayoutParams params = view.mBrandingImageView.getLayoutParams();
+                params.width = mBrandingImageWidth;
+                params.height = mBrandingImageHeight;
+                view.mBrandingImageView.setLayoutParams(params);
+            }
+            if (mBrandingDrawable != null) {
+                view.mBrandingImageView.setBackground(mBrandingDrawable);
+            }
+            if (mParceledBrandingBitmap != null) {
+                view.mParceledBrandingBitmap = mParceledBrandingBitmap;
+            }
+            if (DEBUG) {
+                Log.d(TAG, " build " + view + " Icon: view: " + view.mIconView + " drawable: "
+                        + mIconDrawable + " size: " + mIconSize + "\n Branding: view: "
+                        + view.mBrandingImageView + " drawable: " + mBrandingDrawable
+                        + " size w: " + mBrandingImageWidth + " h: " + mBrandingImageHeight);
+            }
+            return view;
+        }
+    }
+
+    /** @hide */
+    public SplashScreenView(Context context) {
+        super(context);
+    }
+
+    /** @hide */
+    public SplashScreenView(Context context, AttributeSet attributeSet) {
+        super(context, attributeSet);
+    }
+
+    /**
+     * Declared this view is not copyable.
+     * @hide
+     */
+    public void setNotCopyable() {
+        mNotCopyable = true;
+    }
+
+    /**
+     * Whether this view is copyable.
+     * @hide
+     */
+    public boolean isCopyable() {
+        return !mNotCopyable;
+    }
+
+    /**
+     * Returns the duration of the icon animation if icon is animatable.
+     *
+     * @see android.R.attr#windowSplashScreenAnimatedIcon
+     * @see android.R.attr#windowSplashScreenAnimationDuration
+     */
+    public long getIconAnimationDurationMillis() {
+        return mIconAnimationDuration;
+    }
+
+    /**
+     * If the replaced icon is animatable, return the animation start time in millisecond based on
+     * system. The start time is set using {@link SystemClock#uptimeMillis()}.
+     */
+    public long getIconAnimationStartMillis() {
+        return mIconAnimationStart;
+    }
+
+    void initIconAnimation(Drawable iconDrawable, long duration) {
+        if (iconDrawable instanceof Animatable) {
+            mAnimatableIcon = (Animatable) iconDrawable;
+            mAnimator = ValueAnimator.ofInt(0, 1);
+            mAnimator.setDuration(duration);
+            mAnimator.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mIconAnimationStart = SystemClock.uptimeMillis();
+                    mAnimatableIcon.start();
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAnimatableIcon.stop();
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mAnimatableIcon.stop();
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+                    // do not repeat
+                    mAnimatableIcon.stop();
+                }
+            });
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void startIntroAnimation() {
+        if (mAnimatableIcon != null) {
+            mAnimator.start();
+        }
+    }
+
+    /**
+     * <p>Remove this view and release its resource. </p>
+     * <p><strong>Do not</strong> invoke this method from a drawing method
+     * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+     */
+    public void remove() {
+        setVisibility(GONE);
+        if (mParceledIconBitmap != null) {
+            mIconView.setBackground(null);
+            mParceledIconBitmap.recycle();
+            mParceledIconBitmap = null;
+        }
+        if (mParceledBrandingBitmap != null) {
+            mBrandingImageView.setBackground(null);
+            mParceledBrandingBitmap.recycle();
+            mParceledBrandingBitmap = null;
+        }
+        if (mWindow != null) {
+            final DecorView decorView = (DecorView) mWindow.peekDecorView();
+            if (DEBUG) {
+                Log.d(TAG, "remove starting view");
+            }
+            if (decorView != null) {
+                decorView.removeView(this);
+            }
+            restoreSystemUIColors();
+            mWindow = null;
+        }
+    }
+
+    /**
+     * Cache the root window.
+     * @hide
+     */
+    public void cacheRootWindow(Window window) {
+        mWindow = window;
+    }
+
+    /**
+     * Called after SplashScreenView has added on the root window.
+     * @hide
+     */
+    public void makeSystemUIColorsTransparent() {
+        if (mWindow != null) {
+            final WindowManager.LayoutParams attr = mWindow.getAttributes();
+            mDrawBarBackground = (attr.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+            mWindow.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            mStatusBarColor = mWindow.getStatusBarColor();
+            mNavigationBarColor = mWindow.getNavigationBarDividerColor();
+            mWindow.setStatusBarColor(Color.TRANSPARENT);
+            mWindow.setNavigationBarColor(Color.TRANSPARENT);
+        }
+        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+    }
+
+    private void restoreSystemUIColors() {
+        if (mWindow != null) {
+            if (!mDrawBarBackground) {
+                mWindow.clearFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+            }
+            mWindow.setStatusBarColor(mStatusBarColor);
+            mWindow.setNavigationBarColor(mNavigationBarColor);
+        }
+    }
+
+    /**
+     * Get the view containing the Splash Screen icon and its background.
+     * @see android.R.attr#windowSplashScreenAnimatedIcon
+     */
+    public @Nullable View getIconView() {
+        return mIconView;
+    }
+
+    /**
+     * Get the branding image view.
+     * @hide
+     */
+    @TestApi
+    public @Nullable View getBrandingView() {
+        return mBrandingImageView;
+    }
+
+    /**
+     * Get the initial background color of this view.
+     * @hide
+     */
+    @ColorInt int getInitBackgroundColor() {
+        return mInitBackgroundColor;
+    }
+
+    /**
+     * Use to create {@link SplashScreenView} object across process.
+     * @hide
+     */
+    public static class SplashScreenViewParcelable implements Parcelable {
+        private int mIconSize;
+        private int mBackgroundColor;
+
+        private Bitmap mIconBitmap;
+        private int mBrandingWidth;
+        private int mBrandingHeight;
+        private Bitmap mBrandingBitmap;
+
+        private long mIconAnimationStart;
+        private long mIconAnimationDuration;
+
+        public SplashScreenViewParcelable(SplashScreenView view) {
+            ViewGroup.LayoutParams params = view.getIconView().getLayoutParams();
+            mIconSize = params.height;
+            mBackgroundColor = view.getInitBackgroundColor();
+
+            mIconBitmap = copyDrawable(view.getIconView().getBackground());
+            mBrandingBitmap = copyDrawable(view.getBrandingView().getBackground());
+            params = view.getBrandingView().getLayoutParams();
+            mBrandingWidth = params.width;
+            mBrandingHeight = params.height;
+
+            mIconAnimationStart = view.getIconAnimationStartMillis();
+            mIconAnimationDuration = view.getIconAnimationDurationMillis();
+        }
+
+        private Bitmap copyDrawable(Drawable drawable) {
+            if (drawable != null) {
+                final Rect initialBounds = drawable.copyBounds();
+                final int width = initialBounds.width();
+                final int height = initialBounds.height();
+
+                final Bitmap snapshot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                final Canvas bmpCanvas = new Canvas(snapshot);
+                drawable.setBounds(0, 0, width, height);
+                drawable.draw(bmpCanvas);
+                final Bitmap copyBitmap = snapshot.createAshmemBitmap();
+                snapshot.recycle();
+                return copyBitmap;
+            }
+            return null;
+        }
+
+        private SplashScreenViewParcelable(@NonNull Parcel source) {
+            readParcel(source);
+        }
+
+        private void readParcel(@NonNull Parcel source) {
+            mIconSize = source.readInt();
+            mBackgroundColor = source.readInt();
+            mIconBitmap = source.readTypedObject(Bitmap.CREATOR);
+            mBrandingWidth = source.readInt();
+            mBrandingHeight = source.readInt();
+            mBrandingBitmap = source.readTypedObject(Bitmap.CREATOR);
+            mIconAnimationStart = source.readLong();
+            mIconAnimationDuration = source.readLong();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mIconSize);
+            dest.writeInt(mBackgroundColor);
+            dest.writeTypedObject(mIconBitmap, flags);
+            dest.writeInt(mBrandingWidth);
+            dest.writeInt(mBrandingHeight);
+            dest.writeTypedObject(mBrandingBitmap, flags);
+            dest.writeLong(mIconAnimationStart);
+            dest.writeLong(mIconAnimationDuration);
+        }
+
+        public static final @NonNull Parcelable.Creator<SplashScreenViewParcelable> CREATOR =
+                new Parcelable.Creator<SplashScreenViewParcelable>() {
+                    public SplashScreenViewParcelable createFromParcel(@NonNull Parcel source) {
+                        return new SplashScreenViewParcelable(source);
+                    }
+                    public SplashScreenViewParcelable[] newArray(int size) {
+                        return new SplashScreenViewParcelable[size];
+                    }
+                };
+
+        /**
+         * Release the bitmap if another process cannot handle it.
+         */
+        public void clearIfNeeded() {
+            if (mIconBitmap != null) {
+                mIconBitmap.recycle();
+                mIconBitmap = null;
+            }
+            if (mBrandingBitmap != null) {
+                mBrandingBitmap.recycle();
+                mBrandingBitmap = null;
+            }
+        }
+
+        int getIconSize() {
+            return mIconSize;
+        }
+
+        int getBackgroundColor() {
+            return mBackgroundColor;
+        }
+    }
+}
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 2282cc5..63b9e9b 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -95,6 +95,12 @@
      */
     public int startingWindowTypeParameter;
 
+    /**
+     * Specifies a theme for the splash screen.
+     * @hide
+     */
+    public int splashScreenThemeResId;
+
     public StartingWindowInfo() {
 
     }
@@ -115,6 +121,7 @@
         dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
         dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
         dest.writeTypedObject(mainWindowLayoutParams, flags);
+        dest.writeInt(splashScreenThemeResId);
     }
 
     void readFromParcel(@NonNull Parcel source) {
@@ -124,6 +131,7 @@
         topOpaqueWindowLayoutParams = source.readTypedObject(
                 WindowManager.LayoutParams.CREATOR);
         mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
+        splashScreenThemeResId = source.readInt();
     }
 
     @Override
@@ -135,7 +143,8 @@
                 + Integer.toHexString(startingWindowTypeParameter)
                 + " insetsState=" + topOpaqueWindowInsetsState
                 + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams
-                + " mainWindowLayoutParams=" + mainWindowLayoutParams;
+                + " mainWindowLayoutParams=" + mainWindowLayoutParams
+                + " splashScreenThemeResId " + Integer.toHexString(splashScreenThemeResId);
     }
 
     public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index cdb4762..217ade8 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -105,6 +105,12 @@
     public void removeStartingWindow(int taskId) {}
 
     /**
+     * Called when the Task want to copy the splash screen.
+     */
+    @BinderThread
+    public void copySplashScreenView(int taskId) {}
+
+    /**
      * Called when a task with the registered windowing mode can be controlled by this task
      * organizer. For non-root tasks, the leash may initially be hidden so it is up to the organizer
      * to show this task.
@@ -223,6 +229,11 @@
         }
 
         @Override
+        public void copySplashScreenView(int taskId) {
+            mExecutor.execute(() -> TaskOrganizer.this.copySplashScreenView(taskId));
+        }
+
+        @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
             mExecutor.execute(() -> TaskOrganizer.this.onTaskAppeared(taskInfo, leash));
         }
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index c2ee646..0152387 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -53,10 +53,8 @@
     public static final String KEY_CUR_TASK = "cur_task";
     /** Package of newly requested heavy-weight app. */
     public static final String KEY_NEW_APP = "new_app";
-    public static final String KEY_ACTIVITY_OPTIONS = "activity_options";
     
     IntentSender mStartIntent;
-    Bundle mActivityOptions;
     boolean mHasResult;
     String mCurApp;
     int mCurTask;
@@ -67,9 +65,8 @@
         super.onCreate(savedInstanceState);
         
         requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+        
         mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
-        mActivityOptions = getIntent().getBundleExtra(KEY_ACTIVITY_OPTIONS);
         mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
         mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
         mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
@@ -151,9 +148,9 @@
                 if (mHasResult) {
                     startIntentSenderForResult(mStartIntent, -1, null,
                             Intent.FLAG_ACTIVITY_FORWARD_RESULT,
-                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0, mActivityOptions);
+                            Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
                 } else {
-                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0, mActivityOptions);
+                    startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
                 }
             } catch (IntentSender.SendIntentException ex) {
                 Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
similarity index 88%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
index 14d57bf..5d02a29 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.internal.compat;
 
-parcelable ExternalTimeSuggestion;
+parcelable CompatibilityOverrideConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
new file mode 100644
index 0000000..1c222a7
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java
@@ -0,0 +1,75 @@
+/*
+ * 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.internal.compat;
+
+
+import android.app.compat.PackageOverride;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityOverrideConfig implements Parcelable {
+    public final Map<Long, PackageOverride> overrides;
+
+    public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) {
+        this.overrides = overrides;
+    }
+
+    private CompatibilityOverrideConfig(Parcel in) {
+        int keyCount = in.readInt();
+        overrides = new HashMap<>();
+        for (int i = 0; i < keyCount; i++) {
+            long key = in.readLong();
+            PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader());
+            overrides.put(key, override);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(overrides.size());
+        for (Long key : overrides.keySet()) {
+            dest.writeLong(key);
+            dest.writeParcelable(overrides.get(key), 0);
+        }
+    }
+
+    public static final Creator<CompatibilityOverrideConfig> CREATOR =
+            new Creator<CompatibilityOverrideConfig>() {
+
+                @Override
+                public CompatibilityOverrideConfig createFromParcel(Parcel in) {
+                    return new CompatibilityOverrideConfig(in);
+                }
+
+                @Override
+                public CompatibilityOverrideConfig[] newArray(int size) {
+                    return new CompatibilityOverrideConfig[size];
+                }
+            };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7aca36a..249c134 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -21,6 +21,7 @@
 import java.util.Map;
 
 parcelable CompatibilityChangeConfig;
+parcelable CompatibilityOverrideConfig;
 parcelable CompatibilityChangeInfo;
 /**
  * Platform private API for talking with the PlatformCompat service.
@@ -152,6 +153,17 @@
     /**
      * Adds overrides to compatibility changes.
      *
+     * <p>Kills the app to allow the changes to take effect.
+     *
+     * @param overrides   parcelable containing the compat change overrides to be applied
+     * @param packageName the package name of the app whose changes will be overridden
+     * @throws SecurityException if overriding changes is not permitted
+     */
+    void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName);
+
+    /**
+     * Adds overrides to compatibility changes.
+     *
      * <p>Does not kill the app, to be only used in tests.
      *
      * @param overrides   parcelable containing the compat change overrides to be applied
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 0ede1b8..e602cd2 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -28,6 +28,7 @@
 import android.database.MatrixCursor.RowBuilder;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.FileObserver;
@@ -504,7 +505,7 @@
 
         final int pfdMode = ParcelFileDescriptor.parseMode(mode);
         if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
-            return ParcelFileDescriptor.open(file, pfdMode);
+            return openFileForRead(file);
         } else {
             try {
                 // When finished writing, kick off media scanner
@@ -519,6 +520,24 @@
         }
     }
 
+    private ParcelFileDescriptor openFileForRead(final File target) throws FileNotFoundException {
+        final Uri uri = MediaStore.scanFile(getContext().getContentResolver(), target);
+
+        // Passing the calling uid via EXTRA_MEDIA_CAPABILITIES_UID, so that the decision to
+        // transcode or not transcode can be made based upon the calling app's uid, and not based
+        // upon the Provider's uid.
+        final Bundle opts = new Bundle();
+        opts.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID, Binder.getCallingUid());
+
+        final AssetFileDescriptor afd =
+                getContext().getContentResolver().openTypedAssetFileDescriptor(uri, "*/*", opts);
+        if (afd == null) {
+            return null;
+        }
+
+        return afd.getParcelFileDescriptor();
+    }
+
     /**
      * Test if the file matches the query arguments.
      *
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
similarity index 99%
rename from core/java/com/android/internal/BrightnessSynchronizer.java
rename to core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9049ca5..fae5862 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal;
+package com.android.internal.display;
 
 
 import android.content.ContentResolver;
diff --git a/core/java/com/android/internal/display/OWNERS b/core/java/com/android/internal/display/OWNERS
new file mode 100644
index 0000000..20b75be
--- /dev/null
+++ b/core/java/com/android/internal/display/OWNERS
@@ -0,0 +1,3 @@
+include /services/core/java/com/android/server/display/OWNERS
+
+flc@google.com
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index dfcc914..1c7eca8 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -20,6 +20,8 @@
 import android.graphics.fonts.FontUpdateRequest;
 import android.text.FontConfig;
 
+import java.util.List;
+
 /**
  * System private interface for talking with
  * {@link com.android.server.graphics.fonts.FontManagerService}.
@@ -28,5 +30,7 @@
 interface IFontManager {
     FontConfig getFontConfig();
 
-    int updateFont(int baseVersion, in FontUpdateRequest request);
+    int updateFontFile(in FontUpdateRequest request, int baseVersion);
+
+    int updateFontFamily(in List<FontUpdateRequest> request, int baseVersion);
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e9bc84..cba6af9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -499,7 +499,7 @@
         }
 
         public String getPerfettoTrigger() {
-            return String.format("interaction-jank-monitor-%d", mCujType);
+            return String.format("com.android.telemetry.interaction-jank-monitor-%d", mCujType);
         }
 
         public String getName() {
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 8fe17fb..1313090 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -43,8 +43,7 @@
      */
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = mPowerEstimator.calculatePower(durationMs);
@@ -84,7 +83,7 @@
 
     private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
         if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
-            return mAhToUJ(measuredEnergyUJ);
+            return uJtoMah(measuredEnergyUJ);
         } else {
             return mPowerEstimator.calculatePower(durationMs);
         }
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index af61f91..4f2f973b 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -133,6 +133,12 @@
     public double wakeLockPowerMah;
     public double wifiPowerMah;
     public double systemServiceCpuPowerMah;
+    public double[] customMeasuredPowerMah;
+
+    // Power that is re-attributed to other sippers. For example, for System Server
+    // this represents the power attributed to apps requesting system services.
+    // The value should be negative or zero.
+    public double powerReattributedToOtherSippersMah;
 
     // Do not include this sipper in results because it is included
     // in an aggregate sipper.
@@ -251,6 +257,18 @@
         proportionalSmearMah += other.proportionalSmearMah;
         totalSmearedPowerMah += other.totalSmearedPowerMah;
         systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
+        if (other.customMeasuredPowerMah != null) {
+            if (customMeasuredPowerMah == null) {
+                customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length];
+            }
+            if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) {
+                // This should always be true.
+                for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) {
+                    customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx];
+                }
+            }
+        }
+        powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah;
     }
 
     /**
@@ -264,6 +282,15 @@
                 sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
                 flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
                 + systemServiceCpuPowerMah;
+        if (customMeasuredPowerMah != null) {
+            for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) {
+                totalPowerMah += customMeasuredPowerMah[idx];
+            }
+        }
+
+        // powerAttributedToOtherSippersMah is negative or zero
+        totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah;
+
         totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
 
         return totalPowerMah;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index aa5015a..b20f50d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -347,6 +347,7 @@
             mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
             mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
             mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
+            mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
 
             mPowerCalculators.add(new UserPowerCalculator());
         }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 1f7a7aa..87820a8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -104,6 +104,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.power.MeasuredEnergyStats.StandardEnergyBucket;
 import com.android.internal.util.ArrayUtils;
@@ -1001,8 +1002,12 @@
     int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     /**
-     * Accumulated energy consumption, that is not attributed to individual uids, of various
-     * consumers while on battery.
+     * Accumulated global (generally, device-wide total) energy consumption of various consumers
+     * while on battery.
+     * Its '<b>custom</b> energy buckets' correspond to the
+     * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+     * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+     *
      * If energy consumer data is completely unavailable this will be null.
      */
     @GuardedBy("this")
@@ -7156,20 +7161,34 @@
 
     @Override
     public long getScreenOnEnergy() {
-        if (mGlobalMeasuredEnergyStats == null) {
-            return ENERGY_DATA_UNAVAILABLE;
-        }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
     }
 
     @Override
     public long getScreenDozeEnergy() {
+        return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+    }
+
+    /**
+     * Returns the energy in microjoules that the given standard energy bucket consumed.
+     * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+     *
+     * @param bucket standard energy bucket of interest
+     * @return energy (in microjoules) used for this energy bucket
+     */
+    private long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
         if (mGlobalMeasuredEnergyStats == null) {
             return ENERGY_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats
-                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+        return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
+    }
+
+    @Override
+    public @Nullable long[] getCustomMeasuredEnergiesMicroJoules() {
+        if (mGlobalMeasuredEnergyStats == null) {
+            return null;
+        }
+        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
     }
 
     @Override public long getStartClockTime() {
@@ -7518,9 +7537,16 @@
          */
         private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
 
-        /** Measured energies attributed to this uid while on battery. */
-        // We do not use a SparseArray<LongSamplingCounters> since it would cause lots of
-        // unnecessary timebase references, and we're just going to use on-battery anyway...
+        /**
+         * Measured energies attributed to this uid while on battery.
+         * Its '<b>custom</b> energy buckets' correspond to the
+         * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
+         * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+         *
+         * Will be null if energy consumer data is completely unavailable (in which case
+         * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid
+         * is 0 for every bucket.
+         */
         private MeasuredEnergyStats mUidMeasuredEnergyStats;
 
         /**
@@ -7940,6 +7966,13 @@
                     .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
         }
 
+        /** Adds the given energy to the given custom energy bucket for this uid. */
+        private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket,
+                boolean accumulate) {
+            getOrCreateMeasuredEnergyStatsLocked()
+                    .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate);
+        }
+
         /**
          * Returns the energy used by this uid for a standard energy bucket of interest.
          * @param bucket standard energy bucket of interest
@@ -7956,6 +7989,18 @@
             return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
         }
 
+        @Override
+        public long[] getCustomMeasuredEnergiesMicroJoules() {
+            if (mBsi.mGlobalMeasuredEnergyStats == null) {
+                return null;
+            }
+            if (mUidMeasuredEnergyStats == null) {
+                // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
+                return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomEnergyBuckets()];
+            }
+            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketEnergies();
+        }
+
         /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
@@ -10842,6 +10887,10 @@
         mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
     }
 
+    public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+        return mSystemServerCpuThreadReader.readAbsolute();
+    }
+
     public void setCallback(BatteryCallback cb) {
         mCallback = cb;
     }
@@ -12460,6 +12509,39 @@
     }
 
     /**
+     * Accumulate Custom energy bucket energy, globally and for each app.
+     *
+     * @param totalEnergyUJ energy (microjoules) used for this bucket since this was last called.
+     * @param uidEnergies map of uid->energy (microjoules) for this bucket since last called.
+     *                    Data inside uidEnergies will not be modified (treated immutable).
+     */
+    public void updateCustomMeasuredEnergyDataLocked(int customEnergyBucket,
+            long totalEnergyUJ, @Nullable SparseLongArray uidEnergies) {
+        if (DEBUG_ENERGY) {
+            Slog.d(TAG, "Updating attributed measured energy stats for custom bucket "
+                    + customEnergyBucket
+                    + " with total energy " + totalEnergyUJ
+                    + " and uid energies " + String.valueOf(uidEnergies));
+        }
+        if (mGlobalMeasuredEnergyStats == null) return;
+        if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return;
+
+        mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true);
+
+        if (uidEnergies == null) return;
+        final int numUids = uidEnergies.size();
+        for (int i = 0; i < numUids; i++) {
+            final int uidInt = mapUid(uidEnergies.keyAt(i));
+            final long uidEnergyUJ = uidEnergies.valueAt(i);
+            if (uidEnergyUJ == 0) continue;
+            // TODO(b/180030409): Worry about dead Uids (no longer in BSI) being revived by this,
+            //  or converse problem of not creating a new Uid if its first blame is recorded here.
+            final Uid uidObj = getUidStatsLocked(uidInt);
+            uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true);
+        }
+    }
+
+    /**
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
@@ -12928,16 +13010,17 @@
         mWakeLockAllocationsUs = null;
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidFreqTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.d(TAG, "Got freq readings for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
-                mCpuUidFreqTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
@@ -12996,6 +13079,9 @@
                 }
             }
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidFreqTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13042,21 +13128,25 @@
     public void readKernelUidCpuActiveTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidActiveTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.w(TAG, "Got active times for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.w(TAG, "Got active times for an invalid user's uid " + uid);
-                mCpuUidActiveTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
             u.mCpuActiveTimeMs.addCountLocked(cpuActiveTimesMs, onBattery);
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidActiveTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -13072,21 +13162,25 @@
     public void readKernelUidCpuClusterTimesLocked(boolean onBattery) {
         final long startTimeMs = mClocks.uptimeMillis();
         final long elapsedRealtimeMs = mClocks.elapsedRealtime();
+        final List<Integer> uidsToRemove = new ArrayList<>();
         mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> {
             uid = mapUid(uid);
             if (Process.isIsolated(uid)) {
-                mCpuUidClusterTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 if (DEBUG) Slog.w(TAG, "Got cluster times for an isolated uid: " + uid);
                 return;
             }
             if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
                 if (DEBUG) Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid);
-                mCpuUidClusterTimeReader.removeUid(uid);
+                uidsToRemove.add(uid);
                 return;
             }
             final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, startTimeMs);
             u.mCpuClusterTimesMs.addCountLocked(cpuClusterTimesMs, onBattery);
         });
+        for (int uid : uidsToRemove) {
+            mCpuUidClusterTimeReader.removeUid(uid);
+        }
 
         final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
         if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
@@ -14449,8 +14543,13 @@
      */
     @GuardedBy("this")
     public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
-        if (mGlobalMeasuredEnergyStats == null) return;
-        dumpMeasuredEnergyStatsLocked(pw, "non-uid usage", mGlobalMeasuredEnergyStats);
+        pw.printf("On battery measured energy stats (microjoules) \n");
+        if (mGlobalMeasuredEnergyStats == null) {
+            pw.printf("    Not supported on this device.\n");
+            return;
+        }
+
+        dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats);
 
         int size = mUidStats.size();
         for (int i = 0; i < size; i++) {
@@ -14467,7 +14566,8 @@
             MeasuredEnergyStats stats) {
         if (stats == null) return;
         final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "    ");
-        iPw.printf("On battery measured energy stats for %s:\n", name);
+        iPw.increaseIndent();
+        iPw.printf("%s:\n", name);
         iPw.increaseIndent();
         stats.dump(iPw);
         iPw.decreaseIndent();
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index e76e34f..233ba19 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -24,7 +24,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.SparseArray;
 
 import java.util.ArrayList;
@@ -71,10 +70,14 @@
                 mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
-                mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
-
+                mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new UserPowerCalculator());
+
+                // It is important that SystemServicePowerCalculator be applied last,
+                // because it re-attributes some of the power estimated by the other
+                // calculators.
+                mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
             }
         }
         return mPowerCalculators;
@@ -89,26 +92,27 @@
         final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
                 false /* collectBatteryBroadcast */);
         batteryStatsHelper.create((Bundle) null);
-        final UserManager userManager = mContext.getSystemService(UserManager.class);
-        final List<UserHandle> asUsers = userManager.getUserProfiles();
-        final int n = asUsers.size();
-        SparseArray<UserHandle> users = new SparseArray<>(n);
-        for (int i = 0; i < n; ++i) {
-            UserHandle userHandle = asUsers.get(i);
-            users.put(userHandle.getIdentifier(), userHandle);
+        final List<UserHandle> users = new ArrayList<>();
+        for (int i = 0; i < queries.size(); i++) {
+            BatteryUsageStatsQuery query = queries.get(i);
+            for (int userId : query.getUserIds()) {
+                UserHandle userHandle = UserHandle.of(userId);
+                if (!users.contains(userHandle)) {
+                    users.add(userHandle);
+                }
+            }
         }
-
         batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
 
         ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
         for (int i = 0; i < queries.size(); i++) {
-            results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper, users));
+            results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper));
         }
         return results;
     }
 
     private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
-            BatteryStatsHelper batteryStatsHelper, SparseArray<UserHandle> users) {
+            BatteryStatsHelper batteryStatsHelper) {
         // TODO(b/174186358): read extra power component number from configuration
         final int customPowerComponentCount = 0;
         final int customTimeComponentCount = 0;
@@ -127,9 +131,10 @@
         final long uptimeUs = SystemClock.uptimeMillis() * 1000;
 
         final List<PowerCalculator> powerCalculators = getPowerCalculators();
-        for (PowerCalculator powerCalculator : powerCalculators) {
-            powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs, query,
-                    users);
+        for (int i = 0, count = powerCalculators.size(); i < count; i++) {
+            PowerCalculator powerCalculator = powerCalculators.get(i);
+            powerCalculator.calculate(batteryUsageStatsBuilder, mStats, realtimeUs, uptimeUs,
+                    query);
         }
 
         return batteryUsageStatsBuilder.build();
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 4c3b950..7d42de4 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -50,8 +50,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
             return;
         }
@@ -68,7 +67,7 @@
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             calculateApp(app, total);
             if (app.getUid() == Process.BLUETOOTH_UID) {
-                app.setSystemComponent(true);
+                app.excludeFromBatteryUsageStats();
                 systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
             }
         }
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 11c8761..45d8128 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -17,81 +17,166 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.List;
 
 public class CpuPowerCalculator extends PowerCalculator {
     private static final String TAG = "CpuPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
-    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
-    private final PowerProfile mProfile;
+    private final int mNumCpuClusters;
+
+    // Time-in-state based CPU power estimation model computes the estimated power
+    // by adding up three components:
+    //   - CPU Active power:    the constant amount of charge consumed by the CPU when it is on
+    //   - Per Cluster power:   the additional amount of charge consumed by a CPU cluster
+    //                          when it is running
+    //   - Per frequency power: the additional amount of charge caused by dynamic frequency scaling
+
+    private final UsageBasedPowerEstimator mCpuActivePowerEstimator;
+    // One estimator per cluster
+    private final UsageBasedPowerEstimator[] mPerClusterPowerEstimators;
+    // Multiple estimators per cluster: one per available scaling frequency. Note that different
+    // clusters have different sets of frequencies and corresponding power consumption averages.
+    private final UsageBasedPowerEstimator[][] mPerCpuFreqPowerEstimators;
+
+    private static class Result {
+        public long durationMs;
+        public double powerMah;
+        public long durationFgMs;
+        public String packageWithHighestDrain;
+    }
 
     public CpuPowerCalculator(PowerProfile profile) {
-        mProfile = profile;
+        mNumCpuClusters = profile.getNumCpuClusters();
+
+        mCpuActivePowerEstimator = new UsageBasedPowerEstimator(
+                profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE));
+
+        mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters];
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator(
+                    profile.getAveragePowerForCpuCluster(cluster));
+        }
+
+        mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters][];
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster);
+            mPerCpuFreqPowerEstimators[cluster] = new UsageBasedPowerEstimator[speedsForCluster];
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                mPerCpuFreqPowerEstimators[cluster][speed] =
+                        new UsageBasedPowerEstimator(
+                                profile.getAveragePowerForCpuCore(cluster, speed));
+            }
+        }
     }
 
     @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        final int statsType = BatteryStats.STATS_SINCE_CHARGED;
+        Result result = new Result();
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(app, app.getBatteryStatsUid(), result);
+        }
+    }
 
-        long cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
-        final int numClusters = mProfile.getNumCpuClusters();
+    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, Result result) {
+        calculatePowerAndDuration(u, BatteryStats.STATS_SINCE_CHARGED, result);
 
-        double cpuPowerMaUs = 0;
-        for (int cluster = 0; cluster < numClusters; cluster++) {
-            final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
-            for (int speed = 0; speed < speedsForCluster; speed++) {
-                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
-                final double cpuSpeedStepPower = timeUs *
-                        mProfile.getAveragePowerForCpuCore(cluster, speed);
-                if (DEBUG) {
-                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
-                            + speed + " timeUs=" + timeUs + " power="
-                            + formatCharge(cpuSpeedStepPower / MICROSEC_IN_HR));
-                }
-                cpuPowerMaUs += cpuSpeedStepPower;
+        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, result.powerMah)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, result.durationMs)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND,
+                        result.durationFgMs)
+                .setPackageWithHighestDrain(result.packageWithHighestDrain);
+    }
+
+    @Override
+    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
+        Result result = new Result();
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                calculateApp(app, app.uidObj, statsType, result);
             }
         }
-        cpuPowerMaUs += u.getCpuActiveTime() * 1000 * mProfile.getAveragePower(
-                PowerProfile.POWER_CPU_ACTIVE);
+    }
+
+    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
+        calculatePowerAndDuration(u, statsType, result);
+
+        app.cpuPowerMah = result.powerMah;
+        app.cpuTimeMs = result.durationMs;
+        app.cpuFgTimeMs = result.durationFgMs;
+        app.packageWithHighestDrain = result.packageWithHighestDrain;
+    }
+
+    private void calculatePowerAndDuration(BatteryStats.Uid u, int statsType, Result result) {
+        long durationMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
+
+        // Constant battery drain when CPU is active
+        double powerMah = mCpuActivePowerEstimator.calculatePower(u.getCpuActiveTime());
+
+        // Additional per-cluster battery drain
         long[] cpuClusterTimes = u.getCpuClusterTimes();
         if (cpuClusterTimes != null) {
-            if (cpuClusterTimes.length == numClusters) {
-                for (int i = 0; i < numClusters; i++) {
-                    double power =
-                            cpuClusterTimes[i] * 1000 * mProfile.getAveragePowerForCpuCluster(i);
-                    cpuPowerMaUs += power;
+            if (cpuClusterTimes.length == mNumCpuClusters) {
+                for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+                    double power = mPerClusterPowerEstimators[cluster]
+                            .calculatePower(cpuClusterTimes[cluster]);
+                    powerMah += power;
                     if (DEBUG) {
-                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + i + " clusterTimeUs="
-                                + cpuClusterTimes[i] + " power="
-                                + formatCharge(power / MICROSEC_IN_HR));
+                        Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
+                                + " clusterTimeMs=" + cpuClusterTimes[cluster]
+                                + " power=" + formatCharge(power));
                     }
                 }
             } else {
                 Log.w(TAG, "UID " + u.getUid() + " CPU cluster # mismatch: Power Profile # "
-                        + numClusters + " actual # " + cpuClusterTimes.length);
+                        + mNumCpuClusters + " actual # " + cpuClusterTimes.length);
             }
         }
-        final double cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
 
-        if (DEBUG && (cpuTimeMs != 0 || cpuPowerMah != 0)) {
-            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + cpuTimeMs + " ms power="
-                    + formatCharge(cpuPowerMah));
+        // Additional per-frequency battery drain
+        for (int cluster = 0; cluster < mNumCpuClusters; cluster++) {
+            final int speedsForCluster = mPerCpuFreqPowerEstimators[cluster].length;
+            for (int speed = 0; speed < speedsForCluster; speed++) {
+                final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+                final double power =
+                        mPerCpuFreqPowerEstimators[cluster][speed].calculatePower(timeUs / 1000);
+                if (DEBUG) {
+                    Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
+                            + speed + " timeUs=" + timeUs + " power="
+                            + formatCharge(power));
+                }
+                powerMah += power;
+            }
+        }
+
+        if (DEBUG && (durationMs != 0 || powerMah != 0)) {
+            Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power="
+                    + formatCharge(powerMah));
         }
 
         // Keep track of the package with highest drain.
         double highestDrain = 0;
         String packageWithHighestDrain = null;
-        long cpuFgTimeMs = 0;
+        long durationFgMs = 0;
         final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
         final int processStatsCount = processStats.size();
         for (int i = 0; i < processStatsCount; i++) {
             final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
             final String processName = processStats.keyAt(i);
-            cpuFgTimeMs += ps.getForegroundTime(statsType);
+            durationFgMs += ps.getForegroundTime(statsType);
 
             final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
                     + ps.getForegroundTime(statsType);
@@ -107,20 +192,19 @@
             }
         }
 
-
         // Ensure that the CPU times make sense.
-        if (cpuFgTimeMs > cpuTimeMs) {
-            if (DEBUG && cpuFgTimeMs > cpuTimeMs + 10000) {
+        if (durationFgMs > durationMs) {
+            if (DEBUG && durationFgMs > durationMs + 10000) {
                 Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
             }
 
             // Statistics may not have been gathered yet.
-            cpuTimeMs = cpuFgTimeMs;
+            durationMs = durationFgMs;
         }
 
-        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
-                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
-                .setPackageWithHighestDrain(packageWithHighestDrain);
+        result.durationMs = durationMs;
+        result.durationFgMs = durationFgMs;
+        result.powerMah = powerMah;
+        result.packageWithHighestDrain = packageWithHighestDrain;
     }
 }
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
new file mode 100644
index 0000000..4babe8d
--- /dev/null
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.internal.os;
+
+import android.os.BatteryStats;
+
+/**
+ * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
+ * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
+ */
+public class CustomMeasuredPowerCalculator extends PowerCalculator {
+    public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
+    }
+
+    @Override
+    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {
+        updateCustomMeasuredPowerMah(app, u.getCustomMeasuredEnergiesMicroJoules());
+    }
+
+    private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredEnergiesUJ) {
+        sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredEnergiesUJ);
+    }
+
+    private double[] calculateMeasuredEnergiesMah(long[] measuredEnergiesUJ) {
+        if (measuredEnergiesUJ == null) {
+            return null;
+        }
+        final double[] measuredEnergiesMah = new double[measuredEnergiesUJ.length];
+        for (int i = 0; i < measuredEnergiesUJ.length; i++) {
+            measuredEnergiesMah[i] = uJtoMah(measuredEnergiesUJ[i]);
+        }
+        return measuredEnergiesMah;
+    }
+}
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 9ea934a..df25cda 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -45,8 +45,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index dcc8a15..4a4991b 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -49,8 +49,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         if (mPowerMah != 0) {
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
similarity index 69%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to core/java/com/android/internal/os/KernelCpuBpfTracking.java
index 14d57bf..2852547 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/core/java/com/android/internal/os/KernelCpuBpfTracking.java
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.internal.os;
 
-parcelable ExternalTimeSuggestion;
+/** CPU tracking using eBPF. */
+public final class KernelCpuBpfTracking {
+    private KernelCpuBpfTracking() {
+    }
+
+    /** Returns whether CPU tracking using eBPF is supported. */
+    public static native boolean isSupported();
+}
diff --git a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
index 50331e3..06760e1 100644
--- a/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
+++ b/core/java/com/android/internal/os/KernelCpuTotalBpfMapReader.java
@@ -23,9 +23,6 @@
     private KernelCpuTotalBpfMapReader() {
     }
 
-    /** Returns whether total CPU time is measured. */
-    public static native boolean isSupported();
-
     /** Reads total CPU time from bpf map. */
     public static native boolean read(Callback callback);
 
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index df46058..21dcce9 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -26,8 +26,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index e3bd64d..22001d4 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -85,8 +85,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
 
         PowerAndDuration total = new PowerAndDuration();
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 6ab8c90..362ca07 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -39,8 +39,7 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
         final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 05fcc70..7c45cc0 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -67,17 +67,10 @@
      * @param batteryStats  The recorded battery stats.
      * @param rawRealtimeUs The raw system realtime in microseconds.
      * @param rawUptimeUs   The raw system uptime in microseconds.
-     * @param statsType     The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this
-     *                      can only be {@link BatteryStats#STATS_SINCE_CHARGED}, since
-     *                      {@link BatteryStats#STATS_CURRENT} and
-     *                      {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated.
-     * @param asUsers       An array of users for which the attribution is requested.  It may
-     *                      contain {@link UserHandle#USER_ALL} to indicate that the attribution
-     *                      should be performed for all users.
+     * @param query         The query parameters for the calculator.
      */
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -99,19 +92,6 @@
      */
     protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                                       long rawUptimeUs, int statsType) {
-
-        // TODO(b/175156498): Temporary code during the transition from BatterySippers to
-        //  BatteryConsumers.
-        UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, u);
-        calculateApp(builder, u, rawRealtimeUs, rawUptimeUs, BatteryUsageStatsQuery.DEFAULT);
-        final UidBatteryConsumer uidBatteryConsumer = builder.build();
-        app.cpuPowerMah = uidBatteryConsumer.getConsumedPower(
-                UidBatteryConsumer.POWER_COMPONENT_CPU);
-        app.cpuTimeMs = uidBatteryConsumer.getUsageDurationMillis(
-                UidBatteryConsumer.TIME_COMPONENT_CPU);
-        app.cpuFgTimeMs = uidBatteryConsumer.getUsageDurationMillis(
-                UidBatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND);
-        app.packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
     }
 
     /**
@@ -163,7 +143,11 @@
         return String.format(Locale.ENGLISH, format, power);
     }
 
-    static double mAhToUJ(long energyUJ) {
+    static double uJtoMah(long energyUJ) {
+        if (energyUJ == 0) {
+            return 0;
+        }
+
         // TODO(b/173765509): Convert properly. This is mJ / V * (h/3600s) = mAh with V = 3.7 fixed.
         //                    Leaving for later since desired units of energy have yet to be decided
         return energyUJ / 1000.0 / 3.7  / 3600;
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index c86c795..c1dd7ce 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -21,7 +21,7 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.SystemBatteryConsumer;
-import android.os.SystemClock;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.Slog;
@@ -39,9 +39,17 @@
     private static final String TAG = "ScreenPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
 
+    // Minimum amount of time the screen should be on to start smearing drain to apps
+    public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
+
     private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
     private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
 
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
+
     public ScreenPowerCalculator(PowerProfile powerProfile) {
         mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
@@ -51,19 +59,41 @@
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
-        final long durationMs = computeDuration(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
-        final double powerMah = computePower(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED, durationMs);
-        if (powerMah != 0) {
-            builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
-                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
-                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+        final boolean forceUsePowerProfileModel = (query.getFlags()
+                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
+
+        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+                batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
+                forceUsePowerProfileModel);
+
+        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
+                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
+                        totalPowerAndDuration.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
+                        totalPowerAndDuration.powerMah);
+
+        // Now deal with each app's UidBatteryConsumer. The results are stored in the
+        // BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
+        // but the method depends on the data source.
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        if (useEnergyData) {
+            final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+            for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+                final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+                calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
+                        rawRealtimeUs);
+                app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN,
+                                appPowerAndDuration.durationMs)
+                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
+                                appPowerAndDuration.powerMah);
+            }
+        } else {
+            smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
+                    rawRealtimeUs);
         }
-        // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
-        // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
     }
 
     /**
@@ -72,51 +102,79 @@
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-
-        final long energyUJ = batteryStats.getScreenOnEnergy();
-        final boolean isMeasuredDataAvailable = energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE;
-
-        final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
-        final double powerMah = getMeasuredOrComputedPower(
-                energyUJ, batteryStats, rawRealtimeUs, statsType, durationMs);
-        if (powerMah == 0) {
+        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
+        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
+                batteryStats, rawRealtimeUs, statsType, false);
+        if (totalPowerAndDuration.powerMah == 0) {
             return;
         }
 
         // First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
         final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
-        bs.usagePowerMah = powerMah;
-        bs.usageTimeMs = durationMs;
+        bs.usagePowerMah = totalPowerAndDuration.powerMah;
+        bs.usageTimeMs = totalPowerAndDuration.durationMs;
         bs.sumPower();
         sippers.add(bs);
 
         // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
         // field, which is considered smeared, but the method depends on the data source.
-        if (isMeasuredDataAvailable) {
-            super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        if (useEnergyData) {
+            final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
+            for (int i = sippers.size() - 1; i >= 0; i--) {
+                final BatterySipper app = sippers.get(i);
+                if (app.drainType == BatterySipper.DrainType.APP) {
+                    calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj, rawRealtimeUs);
+                    app.screenPowerMah = appPowerAndDuration.powerMah;
+                }
+            }
         } else {
-            smearScreenBatterySipper(sippers, bs);
+            smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
         }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
+    /**
+     * Stores duration and power information in totalPowerAndDuration and returns true if measured
+     * energy data is available and should be used by the model.
+     */
+    private boolean calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
+            BatteryStats batteryStats, long rawRealtimeUs, int statsType,
+            boolean forceUsePowerProfileModel) {
+        totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
+                statsType);
+
+        if (!forceUsePowerProfileModel) {
+            final long energyUJ = batteryStats.getScreenOnEnergy();
+            if (energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+                totalPowerAndDuration.powerMah = uJtoMah(energyUJ);
+                return true;
+            }
+        }
+
+        totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
+                rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
+        return false;
+    }
+
+    private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
+            BatteryStats.Uid u, long rawRealtimeUs) {
+        appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
+
         final long energyUJ = u.getScreenOnEnergy();
         if (energyUJ < 0) {
             Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
+            appPowerAndDuration.powerMah = 0;
             return;
         }
-        if (energyUJ == 0) return;
-        app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());
+
+        appPowerAndDuration.powerMah = uJtoMah(energyUJ);
     }
 
-    private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
+    private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
         return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
     }
 
-    private double computePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType,
-            long durationMs) {
+    private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
+            int statsType, long durationMs) {
         double power = mScreenOnPowerEstimator.calculatePower(durationMs);
         for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             final long brightnessTime =
@@ -132,35 +190,25 @@
         return power;
     }
 
-    private double getMeasuredOrComputedPower(long measuredEnergyUJ,
-            BatteryStats batteryStats, long rawRealtimeUs, int statsType, long durationMs) {
-
-        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
-            return mAhToUJ(measuredEnergyUJ);
-        } else {
-            return computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
-        }
-    }
-
     /**
      * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
      * time, and store this in the {@link BatterySipper#screenPowerMah} field.
      */
     @VisibleForTesting
-    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
-
+    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
+            long rawRealtimeUs) {
         long totalActivityTimeMs = 0;
         final SparseLongArray activityTimeArray = new SparseLongArray();
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatteryStats.Uid uid = sippers.get(i).uidObj;
             if (uid != null) {
-                final long timeMs = getProcessForegroundTimeMs(uid);
+                final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
                 activityTimeArray.put(uid.getUid(), timeMs);
                 totalActivityTimeMs += timeMs;
             }
         }
 
-        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+        if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
             final double totalScreenPowerMah = screenSipper.totalPowerMah;
             for (int i = sippers.size() - 1; i >= 0; i--) {
                 final BatterySipper sipper = sippers.get(i);
@@ -171,10 +219,37 @@
         }
     }
 
+    /**
+     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
+     * time, and store this in the {@link BatterySipper#screenPowerMah} field.
+     */
+    private void smearScreenBatteryDrain(
+            SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
+            PowerAndDuration totalPowerAndDuration, long rawRealtimeUs) {
+        long totalActivityTimeMs = 0;
+        final SparseLongArray activityTimeArray = new SparseLongArray();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
+            final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
+            activityTimeArray.put(uid.getUid(), timeMs);
+            totalActivityTimeMs += timeMs;
+        }
+
+        if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
+            final double totalScreenPowerMah = totalPowerAndDuration.powerMah;
+            for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+                final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+                final long durationMs = activityTimeArray.get(app.getUid(), 0);
+                final double powerMah = totalScreenPowerMah * durationMs / totalActivityTimeMs;
+                app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN, durationMs)
+                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah);
+            }
+        }
+    }
+
     /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
     @VisibleForTesting
-    public long getProcessForegroundTimeMs(BatteryStats.Uid uid) {
-        final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
+    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
         final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
 
         long timeUs = 0;
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbad75e..3ed59f1 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -110,4 +110,24 @@
 
         return mDeltaCpuThreadTimes;
     }
+
+    /** Returns CPU times, per thread group, since tracking started. */
+    @Nullable
+    public SystemServiceCpuThreadTimes readAbsolute() {
+        final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
+        final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
+                mKernelCpuThreadReader.getProcessCpuUsage();
+        if (processCpuUsage == null) {
+            return null;
+        }
+        final SystemServiceCpuThreadTimes result = new SystemServiceCpuThreadTimes();
+        result.threadCpuTimesUs = new long[numCpuFrequencies];
+        result.binderThreadCpuTimesUs = new long[numCpuFrequencies];
+        for (int i = 0; i < numCpuFrequencies; ++i) {
+            result.threadCpuTimesUs[i] = processCpuUsage.threadCpuTimesMillis[i] * 1_000;
+            result.binderThreadCpuTimesUs[i] =
+                    processCpuUsage.selectedThreadCpuTimesMillis[i] * 1_000;
+        }
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 55fc1bb..5c0eeb2 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -20,12 +20,12 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -36,88 +36,112 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "SystemServicePowerCalc";
 
-    private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
-
-    private final PowerProfile mPowerProfile;
-
-    // Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
-    // Data organized like this:
+    // Power estimators per CPU cluster, per CPU frequency. The array is flattened according
+    // to this layout:
     // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
-    private double[] mSystemServicePowerMaUs;
+    private final UsageBasedPowerEstimator[] mPowerEstimators;
 
     public SystemServicePowerCalculator(PowerProfile powerProfile) {
-        mPowerProfile = powerProfile;
+        int numFreqs = 0;
+        final int numCpuClusters = powerProfile.getNumCpuClusters();
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+        }
+
+        mPowerEstimators = new UsageBasedPowerEstimator[numFreqs];
+        int index = 0;
+        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+            final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster);
+            for (int speed = 0; speed < numSpeeds; speed++) {
+                mPowerEstimators[index++] = new UsageBasedPowerEstimator(
+                        powerProfile.getAveragePowerForCpuCore(cluster, speed));
+            }
+        }
     }
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query,
-            SparseArray<UserHandle> asUsers) {
-        calculateSystemServicePower(batteryStats);
-        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query, asUsers);
-    }
-
-    @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
-                calculateSystemServerCpuPowerMah(u));
+        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        final UidBatteryConsumer.Builder systemServerConsumer = uidBatteryConsumerBuilders.get(
+                Process.SYSTEM_UID);
+
+        if (systemServerConsumer != null) {
+            systemServicePowerMah = Math.min(systemServicePowerMah,
+                    systemServerConsumer.getTotalPower());
+
+            // The system server power needs to be adjusted because part of it got
+            // distributed to applications
+            systemServerConsumer.setConsumedPower(
+                    BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
+                    -systemServicePowerMah);
+        }
+
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            if (app != systemServerConsumer) {
+                final BatteryStats.Uid uid = app.getBatteryStatsUid();
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+                        systemServicePowerMah * uid.getProportionalSystemServiceUsage());
+            }
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType,
             SparseArray<UserHandle> asUsers) {
-        calculateSystemServicePower(batteryStats);
-        super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        double systemServicePowerMah = calculateSystemServicePower(batteryStats);
+        BatterySipper systemServerSipper = null;
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                if (app.getUid() == Process.SYSTEM_UID) {
+                    systemServerSipper = app;
+                    break;
+                }
+            }
+        }
+
+        if (systemServerSipper != null) {
+            systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower());
+
+            // The system server power needs to be adjusted because part of it got
+            // distributed to applications
+            systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah;
+        }
+
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                if (app != systemServerSipper) {
+                    final BatteryStats.Uid uid = app.uidObj;
+                    app.systemServiceCpuPowerMah =
+                            systemServicePowerMah * uid.getProportionalSystemServiceUsage();
+                }
+            }
+        }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        app.systemServiceCpuPowerMah = calculateSystemServerCpuPowerMah(u);
-    }
-
-    private void calculateSystemServicePower(BatteryStats batteryStats) {
+    private double calculateSystemServicePower(BatteryStats batteryStats) {
         final long[] systemServiceTimeAtCpuSpeeds = batteryStats.getSystemServiceTimeAtCpuSpeeds();
         if (systemServiceTimeAtCpuSpeeds == null) {
-            return;
+            return 0;
         }
 
-        if (mSystemServicePowerMaUs == null) {
-            mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length];
-        }
-        int index = 0;
-        final int numCpuClusters = mPowerProfile.getNumCpuClusters();
-        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
-            final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
-            for (int speed = 0; speed < numSpeeds; speed++) {
-                mSystemServicePowerMaUs[index] =
-                        systemServiceTimeAtCpuSpeeds[index]
-                                * mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
-                index++;
-            }
+        // TODO(179210707): additionally account for CPU active and per cluster battery use
+
+        double powerMah = 0;
+        for (int i = 0; i < mPowerEstimators.length; i++) {
+            powerMah += mPowerEstimators[i].calculatePower(systemServiceTimeAtCpuSpeeds[i]);
         }
 
         if (DEBUG) {
-            Log.d(TAG, "System service power per CPU cluster and frequency:"
-                    + Arrays.toString(mSystemServicePowerMaUs));
+            Log.d(TAG, "System service power:" + powerMah);
         }
-    }
 
-    private double calculateSystemServerCpuPowerMah(BatteryStats.Uid u) {
-        double cpuPowerMaUs = 0;
-        final double proportionalUsage = u.getProportionalSystemServiceUsage();
-        if (proportionalUsage > 0 && mSystemServicePowerMaUs != null) {
-            for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
-                cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
-            }
-        }
-        return cpuPowerMaUs / MICROSEC_IN_HR;
-    }
-
-    @Override
-    public void reset() {
-        mSystemServicePowerMaUs = null;
+        return powerMah;
     }
 }
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index 53f8515..8e80286 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -17,10 +17,15 @@
 package com.android.internal.os;
 
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.SparseArray;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.List;
 
 /**
@@ -29,6 +34,33 @@
 public class UserPowerCalculator extends PowerCalculator {
 
     @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final int[] userIds = query.getUserIds();
+        if (ArrayUtils.contains(userIds, UserHandle.USER_ALL)) {
+            return;
+        }
+
+        SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            UidBatteryConsumer.Builder uidBuilder = uidBatteryConsumerBuilders.valueAt(i);
+            final int uid = uidBuilder.getUid();
+            if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+                continue;
+            }
+
+            final int userId = UserHandle.getUserId(uid);
+            if (!ArrayUtils.contains(userIds, userId)) {
+                uidBuilder.excludeFromBatteryUsageStats();
+                builder.getOrCreateUserBatteryConsumerBuilder(userId)
+                        .addUidBatteryConsumer(uidBuilder);
+            }
+        }
+    }
+
+    @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
         final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 3f68597..0f4767b 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -15,7 +15,12 @@
  */
 package com.android.internal.os;
 
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -26,39 +31,93 @@
 public class WakelockPowerCalculator extends PowerCalculator {
     private static final String TAG = "WakelockPowerCalculator";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
-    private final double mPowerWakelock;
-    private long mTotalAppWakelockTimeMs = 0;
+    private final UsageBasedPowerEstimator mPowerEstimator;
+
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
 
     public WakelockPowerCalculator(PowerProfile profile) {
-        mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_IDLE);
+        mPowerEstimator = new UsageBasedPowerEstimator(
+                profile.getAveragePower(PowerProfile.POWER_CPU_IDLE));
+    }
+
+    @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration result = new PowerAndDuration();
+        UidBatteryConsumer.Builder osBatteryConsumer = null;
+        double osPowerMah = 0;
+        long osDurationMs = 0;
+        long totalAppDurationMs = 0;
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(result, app.getBatteryStatsUid(), rawRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK, result.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+            totalAppDurationMs += result.durationMs;
+
+            if (app.getUid() == Process.ROOT_UID) {
+                osBatteryConsumer = app;
+                osDurationMs = result.durationMs;
+                osPowerMah = result.powerMah;
+            }
+        }
+
+        // The device has probably been awake for longer than the screen on
+        // time and application wake lock time would account for.  Assign
+        // this remainder to the OS, if possible.
+        if (osBatteryConsumer != null) {
+            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
+            osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK,
+                    result.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        final PowerAndDuration result = new PowerAndDuration();
+        BatterySipper osSipper = null;
+        double osPowerMah = 0;
+        long osDurationMs = 0;
+        long totalAppDurationMs = 0;
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatterySipper app = sippers.get(i);
+            if (app.drainType == BatterySipper.DrainType.APP) {
+                calculateApp(result, app.uidObj, rawRealtimeUs, statsType);
+                app.wakeLockTimeMs = result.durationMs;
+                app.wakeLockPowerMah = result.powerMah;
+                totalAppDurationMs += result.durationMs;
+
+                if (app.getUid() == Process.ROOT_UID) {
+                    osSipper = app;
+                    osPowerMah = result.powerMah;
+                    osDurationMs = result.durationMs;
+                }
+            }
+        }
 
         // The device has probably been awake for longer than the screen on
         // time and application wake lock time would account for.  Assign
         // this remainder to the OS, if possible.
-        BatterySipper osSipper = null;
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            BatterySipper app = sippers.get(i);
-            if (app.getUid() == 0) {
-                osSipper = app;
-                break;
-            }
-        }
-
         if (osSipper != null) {
-            calculateRemaining(osSipper, batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
+            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType,
+                    osPowerMah, osDurationMs, totalAppDurationMs);
+            osSipper.wakeLockTimeMs = result.durationMs;
+            osSipper.wakeLockPowerMah = result.powerMah;
             osSipper.sumPower();
         }
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
+    private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs,
+            int statsType) {
         long wakeLockTimeUs = 0;
         final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
                 u.getWakelockStats();
@@ -73,34 +132,29 @@
                 wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
             }
         }
-        app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
-        mTotalAppWakelockTimeMs += app.wakeLockTimeMs;
+        result.durationMs = wakeLockTimeUs / 1000; // convert to millis
 
         // Add cost of holding a wake lock.
-        app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000 * 60 * 60);
-        if (DEBUG && app.wakeLockPowerMah != 0) {
-            Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
-                    + " power=" + formatCharge(app.wakeLockPowerMah));
+        result.powerMah = mPowerEstimator.calculatePower(result.durationMs);
+        if (DEBUG && result.powerMah != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs
+                    + " power=" + formatCharge(result.powerMah));
         }
     }
 
-    private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000;
-        wakeTimeMillis -= mTotalAppWakelockTimeMs
-                + (stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000);
+    private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs,
+            long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs,
+            long totalAppDurationMs) {
+        final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000
+                - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000
+                - totalAppDurationMs;
         if (wakeTimeMillis > 0) {
-            final double power = (wakeTimeMillis * mPowerWakelock) / (1000 * 60 * 60);
+            final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
             if (DEBUG) {
                 Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power));
             }
-            app.wakeLockTimeMs += wakeTimeMillis;
-            app.wakeLockPowerMah += power;
+            result.durationMs = osDurationMs + wakeTimeMillis;
+            result.powerMah = osPowerMah + power;
         }
     }
-
-    @Override
-    public void reset() {
-        mTotalAppWakelockTimeMs = 0;
-    }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index adebde0..9840013 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -110,6 +110,7 @@
 import android.widget.PopupWindow;
 
 import com.android.internal.R;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.policy.PhoneWindow.PanelFeatureState;
 import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback;
 import com.android.internal.view.FloatingActionMode;
@@ -255,6 +256,7 @@
     private Drawable mOriginalBackgroundDrawable;
     private Drawable mLastOriginalBackgroundDrawable;
     private Drawable mResizingBackgroundDrawable;
+    private BackgroundBlurDrawable mBackgroundBlurDrawable;
 
     /**
      * Temporary holder for a window background when it is set before {@link #mWindow} is
@@ -280,9 +282,14 @@
     private final Paint mLegacyNavigationBarBackgroundPaint = new Paint();
     private Insets mBackgroundInsets = Insets.NONE;
     private Insets mLastBackgroundInsets = Insets.NONE;
+    private int mLastBackgroundBlurRadius = 0;
     private boolean mDrawLegacyNavigationBarBackground;
 
     private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
+    private final ViewTreeObserver.OnPreDrawListener mBackgroundBlurOnPreDrawListener = () -> {
+        updateBackgroundBlur();
+        return true;
+    };
 
     DecorView(Context context, int featureId, PhoneWindow window,
             WindowManager.LayoutParams params) {
@@ -1263,18 +1270,27 @@
         if (mBackgroundInsets == null) {
             mBackgroundInsets = Insets.NONE;
         }
+
         if (mBackgroundInsets.equals(mLastBackgroundInsets)
+                && mWindow.mBackgroundBlurRadius == mLastBackgroundBlurRadius
                 && mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
             return;
         }
-        if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) {
 
-            // Call super since we are intercepting setBackground on this class.
-            super.setBackgroundDrawable(mOriginalBackgroundDrawable);
-        } else {
+        Drawable destDrawable = mOriginalBackgroundDrawable;
+        if (mWindow.mBackgroundBlurRadius > 0 && getViewRootImpl() != null
+                && mWindow.isTranslucent()) {
+            if (mBackgroundBlurDrawable == null) {
+                mBackgroundBlurDrawable = getViewRootImpl().createBackgroundBlurDrawable();
+            }
+            destDrawable = new LayerDrawable(new Drawable[] {mBackgroundBlurDrawable,
+                                                             mOriginalBackgroundDrawable});
+            mLastBackgroundBlurRadius = mWindow.mBackgroundBlurRadius;
+        }
 
-            // Call super since we are intercepting setBackground on this class.
-            super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable,
+
+        if (destDrawable != null && !mBackgroundInsets.equals(Insets.NONE)) {
+            destDrawable = new InsetDrawable(destDrawable,
                     mBackgroundInsets.left, mBackgroundInsets.top,
                     mBackgroundInsets.right, mBackgroundInsets.bottom) {
 
@@ -1286,12 +1302,32 @@
                 public boolean getPadding(Rect padding) {
                     return getDrawable().getPadding(padding);
                 }
-            });
+            };
         }
+
+        // Call super since we are intercepting setBackground on this class.
+        super.setBackgroundDrawable(destDrawable);
+
         mLastBackgroundInsets = mBackgroundInsets;
         mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
     }
 
+    private void updateBackgroundBlur() {
+        if (mBackgroundBlurDrawable == null) return;
+
+        // If the blur radius is 0, the blur region won't be sent to surface flinger, so we don't
+        // need to calculate the corner radius.
+        if (mWindow.mBackgroundBlurRadius > 0) {
+            if (mOriginalBackgroundDrawable != null) {
+                final Outline outline = new Outline();
+                mOriginalBackgroundDrawable.getOutline(outline);
+                mBackgroundBlurDrawable.setCornerRadius(outline.mMode == Outline.MODE_ROUND_RECT
+                                                           ? outline.getRadius() : 0);
+            }
+        }
+        mBackgroundBlurDrawable.setBlurRadius(mWindow.mBackgroundBlurRadius);
+    }
+
     @Override
     public Drawable getBackground() {
         return mOriginalBackgroundDrawable;
@@ -1722,6 +1758,9 @@
             cb.onAttachedToWindow();
         }
 
+        getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+        updateBackgroundDrawable();
+
         if (mFeatureId == -1) {
             /*
              * The main window has been attached, try to restore any panels
@@ -1755,6 +1794,8 @@
             cb.onDetachedFromWindow();
         }
 
+        getViewTreeObserver().removeOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
+
         if (mWindow.mDecorContentParent != null) {
             mWindow.mDecorContentParent.dismissPopups();
         }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 5df175e..d06413c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -258,6 +258,8 @@
     Drawable mBackgroundDrawable = null;
     Drawable mBackgroundFallbackDrawable = null;
 
+    int mBackgroundBlurRadius = 0;
+
     private boolean mLoadElevation = true;
     private float mElevation;
 
@@ -1523,6 +1525,15 @@
     }
 
     @Override
+    public final void setBackgroundBlurRadius(int blurRadius) {
+        super.setBackgroundBlurRadius(blurRadius);
+        if (getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_CROSS_LAYER_BLUR)) {
+            mBackgroundBlurRadius = Math.max(blurRadius, 0);
+        }
+    }
+
+    @Override
     public final void setFeatureDrawableResource(int featureId, int resId) {
         if (resId != 0) {
             DrawableFeatureState st = getDrawableState(featureId, true);
@@ -2549,6 +2560,9 @@
                     android.R.styleable.Window_windowBlurBehindRadius, 0);
         }
 
+        setBackgroundBlurRadius(a.getDimensionPixelSize(
+                R.styleable.Window_windowBackgroundBlurRadius, 0));
+
 
         if (params.windowAnimations == 0) {
             params.windowAnimations = a.getResourceId(
diff --git a/core/java/com/android/internal/power/MeasuredEnergyArray.java b/core/java/com/android/internal/power/MeasuredEnergyArray.java
deleted file mode 100644
index 1f6dc26..0000000
--- a/core/java/com/android/internal/power/MeasuredEnergyArray.java
+++ /dev/null
@@ -1,66 +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.
- */
-
-package com.android.internal.power;
-
-
-import android.annotation.IntDef;
-
-import com.android.internal.os.RailStats;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Interface to provide subsystem energy data.
- * TODO: replace this and {@link RailStats} once b/173077356 is done
- */
-public interface MeasuredEnergyArray {
-    int SUBSYSTEM_UNKNOWN = -1;
-    int SUBSYSTEM_DISPLAY = 0;
-    int NUMBER_SUBSYSTEMS = 1;
-    String[] SUBSYSTEM_NAMES = {"display"};
-
-
-    @IntDef(prefix = { "SUBSYSTEM_" }, value = {
-            SUBSYSTEM_UNKNOWN,
-            SUBSYSTEM_DISPLAY,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface MeasuredEnergySubsystem {}
-
-    /**
-     * Get the subsystem at an index in array.
-     *
-     * @param index into the array.
-     * @return subsystem.
-     */
-    @MeasuredEnergySubsystem
-    int getSubsystem(int index);
-
-    /**
-     * Get the energy (in microjoules) consumed since boot of the subsystem at an index.
-     *
-     * @param index into the array.
-     * @return energy (in microjoules) consumed since boot.
-     */
-    long getEnergy(int index);
-
-    /**
-     * Return number of subsystems in the array.
-     */
-    int size();
-}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index e310f8d..d7b4d78 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -28,7 +28,6 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -239,6 +238,7 @@
      * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
      * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
      */
+    @VisibleForTesting
     public long getAccumulatedCustomBucketEnergy(int customBucket) {
         if (!isValidCustomBucket(customBucket)) {
             return ENERGY_DATA_UNAVAILABLE;
@@ -247,7 +247,18 @@
     }
 
     /**
-     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+     * Return accumulated energies (in microjoules) for all custom energy buckets since last reset.
+     */
+    public @NonNull long[] getAccumulatedCustomBucketEnergies() {
+        final long[] energies = new long[getNumberCustomEnergyBuckets()];
+        for (int bucket = 0; bucket < energies.length; bucket++) {
+            energies[bucket] = mAccumulatedEnergiesMicroJoules[customBucketToIndex(bucket)];
+        }
+        return energies;
+    }
+
+    /**
+     * Map {@link android.view.Display} STATE_ to corresponding {@link StandardEnergyBucket}.
      */
     public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
         if (Display.isOnState(screenState)) {
@@ -405,7 +416,6 @@
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Accumulated energy since last reset (microjoules):");
         pw.print("   ");
         for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
             pw.print(getBucketName(index));
@@ -432,6 +442,11 @@
         return "CUSTOM_" + indexToCustomBucket(index);
     }
 
+    /** Get the number of custom energy buckets on this device. */
+    public int getNumberCustomEnergyBuckets() {
+        return mAccumulatedEnergiesMicroJoules.length - NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
     private static int customBucketToIndex(int customBucket) {
         return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
     }
@@ -450,7 +465,9 @@
         return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
     }
 
-    private boolean isValidCustomBucket(int customBucket) {
+    /** Returns whether the given custom bucket is valid (exists) on this device. */
+    @VisibleForTesting
+    public boolean isValidCustomBucket(int customBucket) {
         return customBucket >= 0
                 && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
     }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 488b18d..c6a040c 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -47,6 +47,13 @@
     private CollectionUtils() { /* cannot be instantiated */ }
 
     /**
+     * @see Collection#contains(Object)
+     */
+    public static <T> boolean contains(@Nullable Collection<T> collection, T element) {
+        return collection != null && collection.contains(element);
+    }
+
+    /**
      * Returns a list of items from the provided list that match the given condition.
      *
      * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f42f468..dc6880e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -219,7 +219,7 @@
     }
 
     private static String getTraceTriggerNameForAction(@Action int action) {
-        return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
+        return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
     }
 
     public static boolean isEnabled(Context ctx) {
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
index 9c87c69..c758504 100644
--- a/core/java/com/android/internal/util/PerfettoTrigger.java
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.os.SystemClock;
 import android.util.Log;
 
 import java.io.IOException;
@@ -27,16 +28,28 @@
 public class PerfettoTrigger {
     private static final String TAG = "PerfettoTrigger";
     private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+    private static final long THROTTLE_MILLIS = 60000;
+    private static volatile long sLastTriggerTime = -THROTTLE_MILLIS;
 
     /**
      * @param triggerName The name of the trigger. Must match the value defined in the AOT
      *                    Perfetto config.
      */
     public static void trigger(String triggerName) {
+        // Trace triggering has a non-negligible cost (fork+exec).
+        // To mitigate potential excessive triggering by the API client we ignore calls that happen
+        // too quickl after the most recent trigger.
+        long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime;
+        if (sinceLastTrigger < THROTTLE_MILLIS) {
+            Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger");
+            return;
+        }
+
         try {
             ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
             Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
-            Process process = pb.start();
+            pb.start();
+            sLastTriggerTime = SystemClock.elapsedRealtime();
         } catch (IOException e) {
             Log.w(TAG, "Failed to trigger " + triggerName, e);
         }
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
new file mode 100644
index 0000000..6cc5a4a
--- /dev/null
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.app.Person;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * A custom-built layout for the Notification.CallStyle.
+ */
+@RemoteViews.RemoteView
+public class CallLayout extends FrameLayout {
+    private final PeopleHelper mPeopleHelper = new PeopleHelper();
+
+    private int mLayoutColor;
+    private Icon mLargeIcon;
+    private Person mUser;
+
+    private CachingIconView mConversationIconView;
+    private CachingIconView mIcon;
+    private CachingIconView mConversationIconBadgeBg;
+    private TextView mConversationText;
+
+    public CallLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public CallLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mPeopleHelper.init(getContext());
+        mConversationText = findViewById(R.id.conversation_text);
+        mConversationIconView = findViewById(R.id.conversation_icon);
+        mIcon = findViewById(R.id.icon);
+        mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
+
+        // When the small icon is gone, hide the rest of the badge
+        mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+        });
+    }
+
+    private void updateCallLayout() {
+        CharSequence callerName = "";
+        String symbol = "";
+        Icon icon = null;
+        if (mUser != null) {
+            icon = mUser.getIcon();
+            callerName = mUser.getName();
+            symbol = mPeopleHelper.findNamePrefix(callerName, "");
+        }
+        if (icon == null) {
+            icon = mLargeIcon;
+        }
+        if (icon == null) {
+            icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor);
+        }
+        // TODO(b/179178086): crop/clip the icon to a circle?
+        mConversationIconView.setImageIcon(icon);
+        mConversationText.setText(callerName);
+    }
+
+    @RemotableViewMethod
+    public void setLayoutColor(int color) {
+        mLayoutColor = color;
+    }
+
+    /**
+     * @param color the color of the notification background
+     */
+    @RemotableViewMethod
+    public void setNotificationBackgroundColor(int color) {
+        mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
+    }
+
+    @RemotableViewMethod
+    public void setLargeIcon(Icon largeIcon) {
+        mLargeIcon = largeIcon;
+    }
+
+    /**
+     * Set the notification extras so that this layout has access
+     */
+    @RemotableViewMethod
+    public void setData(Bundle extras) {
+        setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON));
+        updateCallLayout();
+    }
+
+    private void setUser(Person user) {
+        mUser = user;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 40e671f..1b1e0bf 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -18,8 +18,6 @@
 
 import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
 import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
-import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -34,10 +32,7 @@
 import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.GradientDrawable;
@@ -68,14 +63,12 @@
 
 import com.android.internal.R;
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ContrastColorUtil;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.function.Consumer;
-import java.util.regex.Pattern;
 
 /**
  * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -85,16 +78,6 @@
 public class ConversationLayout extends FrameLayout
         implements ImageMessageConsumer, IMessagingLayout {
 
-    private static final float COLOR_SHIFT_AMOUNT = 60;
-    /**
-     *  Pattern for filter some ignorable characters.
-     *  p{Z} for any kind of whitespace or invisible separator.
-     *  p{C} for any kind of punctuation character.
-     */
-    private static final Pattern IGNORABLE_CHAR_PATTERN
-            = Pattern.compile("[\\p{C}\\p{Z}]");
-    private static final Pattern SPECIAL_CHAR_PATTERN
-            = Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
     private static final Consumer<MessagingMessage> REMOVE_MESSAGE
             = MessagingMessage::removeMessage;
     public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -106,6 +89,7 @@
     public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
     public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
     public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
+    private final PeopleHelper mPeopleHelper = new PeopleHelper();
     private List<MessagingMessage> mMessages = new ArrayList<>();
     private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
     private MessagingLinearLayout mMessagingLinearLayout;
@@ -114,9 +98,6 @@
     private int mLayoutColor;
     private int mSenderTextColor;
     private int mMessageTextColor;
-    private int mAvatarSize;
-    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private Paint mTextPaint = new Paint();
     private Icon mAvatarReplacement;
     private boolean mIsOneToOne;
     private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
@@ -196,6 +177,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mPeopleHelper.init(getContext());
         mMessagingLinearLayout = findViewById(R.id.notification_messaging);
         mActions = findViewById(R.id.actions);
         mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
@@ -205,9 +187,6 @@
         int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
         mMessagingClipRect = new Rect(0, 0, size, size);
         setMessagingClippingDisabled(false);
-        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
-        mTextPaint.setTextAlign(Paint.Align.CENTER);
-        mTextPaint.setAntiAlias(true);
         mConversationIconView = findViewById(R.id.conversation_icon);
         mConversationIconContainer = findViewById(R.id.conversation_icon_container);
         mIcon = findViewById(R.id.icon);
@@ -250,15 +229,15 @@
         });
         // When the small icon is gone, hide the rest of the badge
         mIcon.setOnForceHiddenChangedListener((forceHidden) -> {
-            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
-            animateViewForceHidden(mImportanceRingView, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
         });
 
         // When the conversation icon is gone, hide the whole badge
         mConversationIconView.setOnForceHiddenChangedListener((forceHidden) -> {
-            animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
-            animateViewForceHidden(mImportanceRingView, forceHidden);
-            animateViewForceHidden(mIcon, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mConversationIconBadgeBg, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mImportanceRingView, forceHidden);
+            mPeopleHelper.animateViewForceHidden(mIcon, forceHidden);
         });
         mConversationText = findViewById(R.id.conversation_text);
         mExpandButtonContainer = findViewById(R.id.expand_button_container);
@@ -317,28 +296,6 @@
                 R.dimen.notification_header_separating_margin);
     }
 
-    private void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
-        boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
-        if (forceHidden == nowForceHidden) {
-            // We are either already forceHidden or will be
-            return;
-        }
-        view.animate().cancel();
-        view.setWillBeForceHidden(forceHidden);
-        view.animate()
-                .scaleX(forceHidden ? 0.5f : 1.0f)
-                .scaleY(forceHidden ? 0.5f : 1.0f)
-                .alpha(forceHidden ? 0.0f : 1.0f)
-                .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
-                .setDuration(160);
-        if (view.getVisibility() != VISIBLE) {
-            view.setForceHidden(forceHidden);
-        } else {
-            view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
-        }
-        view.animate().start();
-    }
-
     @RemotableViewMethod
     public void setAvatarReplacement(Icon icon) {
         mAvatarReplacement = icon;
@@ -561,7 +518,8 @@
                     if (mConversationIcon == null) {
                         Icon avatarIcon = messagingGroup.getAvatarIcon();
                         if (avatarIcon == null) {
-                            avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
+                            avatarIcon = mPeopleHelper.createAvatarSymbol(conversationText, "",
+                                    mLayoutColor);
                         }
                         mConversationIcon = avatarIcon;
                     }
@@ -674,11 +632,11 @@
             }
         }
         if (lastIcon == null) {
-            lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
+            lastIcon = mPeopleHelper.createAvatarSymbol(" ", "", mLayoutColor);
         }
         bottomView.setImageIcon(lastIcon);
         if (secondLastIcon == null) {
-            secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
+            secondLastIcon = mPeopleHelper.createAvatarSymbol("", "", mLayoutColor);
         }
         topView.setImageIcon(secondLastIcon);
     }
@@ -838,8 +796,10 @@
     }
 
     private void updateTitleAndNamesDisplay() {
+        // Map of unique names to their prefix
         ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
-        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+        // Map of single-character string prefix to the only name which uses it, or null if multiple
+        ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
         for (int i = 0; i < mGroups.size(); i++) {
             MessagingGroup group = mGroups.get(i);
             CharSequence senderName = group.getSenderName();
@@ -847,22 +807,22 @@
                 continue;
             }
             if (!uniqueNames.containsKey(senderName)) {
-                // Only use visible characters to get uniqueNames
-                String pureSenderName = IGNORABLE_CHAR_PATTERN
-                        .matcher(senderName).replaceAll("" /* replacement */);
-                char c = pureSenderName.charAt(0);
-                if (uniqueCharacters.containsKey(c)) {
+                String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
+                if (charPrefix == null) {
+                    continue;
+                }
+                if (uniqueCharacters.containsKey(charPrefix)) {
                     // this character was already used, lets make it more unique. We first need to
                     // resolve the existing character if it exists
-                    CharSequence existingName = uniqueCharacters.get(c);
+                    CharSequence existingName = uniqueCharacters.get(charPrefix);
                     if (existingName != null) {
-                        uniqueNames.put(existingName, findNameSplit((String) existingName));
-                        uniqueCharacters.put(c, null);
+                        uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
+                        uniqueCharacters.put(charPrefix, null);
                     }
-                    uniqueNames.put(senderName, findNameSplit((String) senderName));
+                    uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
                 } else {
-                    uniqueNames.put(senderName, Character.toString(c));
-                    uniqueCharacters.put(c, pureSenderName);
+                    uniqueNames.put(senderName, charPrefix);
+                    uniqueCharacters.put(charPrefix, senderName);
                 }
             }
         }
@@ -898,8 +858,8 @@
             } else {
                 Icon cachedIcon = cachedAvatars.get(senderName);
                 if (cachedIcon == null) {
-                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
-                            mLayoutColor);
+                    cachedIcon = mPeopleHelper.createAvatarSymbol(senderName,
+                            uniqueNames.get(senderName), mLayoutColor);
                     cachedAvatars.put(senderName, cachedIcon);
                 }
                 group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
@@ -908,49 +868,6 @@
         }
     }
 
-    private Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
-        if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
-                SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
-            Icon avatarIcon = Icon.createWithResource(getContext(),
-                    R.drawable.messaging_user);
-            avatarIcon.setTint(findColor(senderName, layoutColor));
-            return avatarIcon;
-        } else {
-            Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(bitmap);
-            float radius = mAvatarSize / 2.0f;
-            int color = findColor(senderName, layoutColor);
-            mPaint.setColor(color);
-            canvas.drawCircle(radius, radius, radius, mPaint);
-            boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
-            mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
-            mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
-            int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
-            canvas.drawText(symbol, radius, yPos, mTextPaint);
-            return Icon.createWithBitmap(bitmap);
-        }
-    }
-
-    private int findColor(CharSequence senderName, int layoutColor) {
-        double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
-        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
-
-        // we need to offset the range if the luminance is too close to the borders
-        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
-        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
-        return ContrastColorUtil.getShiftedColor(layoutColor,
-                (int) (shift * COLOR_SHIFT_AMOUNT));
-    }
-
-    private String findNameSplit(String existingName) {
-        String[] split = existingName.split(" ");
-        if (split.length > 1) {
-            return Character.toString(split[0].charAt(0))
-                    + Character.toString(split[1].charAt(0));
-        }
-        return existingName.substring(0, 1);
-    }
-
     @RemotableViewMethod
     public void setLayoutColor(int color) {
         mLayoutColor = color;
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5213746..058a921 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,17 +16,24 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.DrawableWrapper;
 import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
+import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 
+import com.android.internal.R;
+
 /**
  * A button implementation for the emphasized notification style.
  *
@@ -37,6 +44,7 @@
     private final RippleDrawable mRipple;
     private final int mStrokeWidth;
     private final int mStrokeColor;
+    private boolean mPriority;
 
     public EmphasizedNotificationButton(Context context) {
         this(context, null);
@@ -80,4 +88,57 @@
         inner.setStroke(hasStroke ? mStrokeWidth : 0, mStrokeColor);
         invalidate();
     }
+
+    /**
+     * Sets an image icon which will have its size constrained and will be set to the same color as
+     * the text. Must be called after {@link #setTextColor(int)} for the latter to work.
+     */
+    @RemotableViewMethod(asyncImpl = "setImageIconAsync")
+    public void setImageIcon(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        setImageDrawable(drawable);
+    }
+
+    /**
+     * @hide
+     */
+    @RemotableViewMethod
+    public Runnable setImageIconAsync(@Nullable Icon icon) {
+        final Drawable drawable = icon == null ? null : icon.loadDrawable(mContext);
+        return () -> setImageDrawable(drawable);
+    }
+
+    private void setImageDrawable(Drawable drawable) {
+        if (drawable != null) {
+            drawable.mutate();
+            drawable.setTintList(getTextColors());
+            drawable.setTintBlendMode(BlendMode.SRC_IN);
+            int iconSize = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_actions_icon_drawable_size);
+            drawable.setBounds(0, 0, iconSize, iconSize);
+        }
+        setCompoundDrawablesRelative(drawable, null, null, null);
+    }
+
+    /**
+     * Changes the LayoutParams.width to WRAP_CONTENT, with the argument representing if this view
+     * is a priority over its peers (which affects weight).
+     */
+    @RemotableViewMethod
+    public void setWrapModePriority(boolean priority) {
+        mPriority = priority;
+        ViewGroup.LayoutParams layoutParams = getLayoutParams();
+        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        if (layoutParams instanceof LinearLayout.LayoutParams) {
+            ((LinearLayout.LayoutParams) layoutParams).weight = 0;
+        }
+        setLayoutParams(layoutParams);
+    }
+
+    /**
+     * Sizing this button is a priority compared with its peers.
+     */
+    public boolean isPriority() {
+        return mPriority;
+    }
 }
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index c7ea781..8e6497b 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.widget;
 
+import android.annotation.DimenRes;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.RippleDrawable;
@@ -41,13 +42,16 @@
 
     private final int mGravity;
     private int mTotalWidth = 0;
+    private int mExtraStartPadding = 0;
     private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
     private boolean mEmphasizedMode;
+    private boolean mPrioritizedWrapMode;
     private int mDefaultPaddingBottom;
     private int mDefaultPaddingTop;
     private int mEmphasizedHeight;
     private int mRegularHeight;
+    @DimenRes private int mCollapsibleIndentDimen;
 
     public NotificationActionListLayout(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -68,7 +72,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mEmphasizedMode) {
+        if (mEmphasizedMode && !mPrioritizedWrapMode) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
         }
@@ -151,7 +155,15 @@
             measuredChildren++;
         }
 
-        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft;
+        int collapsibleIndent = mCollapsibleIndentDimen == 0 ? 0
+                : getResources().getDimensionPixelOffset(mCollapsibleIndentDimen);
+        if (innerWidth - usedWidth > collapsibleIndent) {
+            mExtraStartPadding = collapsibleIndent;
+        } else {
+            mExtraStartPadding = 0;
+        }
+
+        mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
         setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                 resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
     }
@@ -163,7 +175,11 @@
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View c = getChildAt(i);
-            if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
+            if (c instanceof EmphasizedNotificationButton
+                    && ((EmphasizedNotificationButton) c).isPriority()) {
+                // add with 0 length to ensure that this view is measured before others.
+                mMeasureOrderTextViews.add(Pair.create(0, (TextView) c));
+            } else if (c instanceof TextView && ((TextView) c).getText().length() > 0) {
                 mMeasureOrderTextViews.add(Pair.create(((TextView) c).getText().length(),
                         (TextView)c));
             } else {
@@ -197,7 +213,7 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (mEmphasizedMode) {
+        if (mEmphasizedMode && !mPrioritizedWrapMode) {
             super.onLayout(changed, left, top, right, bottom);
             return;
         }
@@ -214,6 +230,9 @@
             int absoluteGravity = Gravity.getAbsoluteGravity(Gravity.START, getLayoutDirection());
             if (absoluteGravity == Gravity.RIGHT) {
                 childLeft += right - left - mTotalWidth;
+            } else {
+                // Put the extra start padding (if any) on the left when LTR
+                childLeft += mExtraStartPadding;
             }
         }
 
@@ -274,6 +293,26 @@
     }
 
     /**
+     * When used with emphasizedMode, changes the button sizing behavior to prioritize certain
+     * buttons (which are system generated) to not scrunch, and leave the remaining space for
+     * custom actions.
+     */
+    @RemotableViewMethod
+    public void setPrioritizedWrapMode(boolean prioritizedWrapMode) {
+        mPrioritizedWrapMode = prioritizedWrapMode;
+    }
+
+    /**
+     * When buttons are in wrap mode, this is a padding that will be applied at the start of the
+     * layout of the actions, but only when those actions would fit with the entire padding
+     * visible.  Otherwise, this padding will be omitted entirely.
+     */
+    @RemotableViewMethod
+    public void setCollapsibleIndentDimen(@DimenRes int collapsibleIndentDimen) {
+        mCollapsibleIndentDimen = collapsibleIndentDimen;
+    }
+
+    /**
      * Set whether the list is in a mode where some actions are emphasized. This will trigger an
      * equal measuring where all actions are full height and change a few parameters like
      * the padding.
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
new file mode 100644
index 0000000..77f4c8f
--- /dev/null
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -0,0 +1,179 @@
+/*
+ * 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.internal.widget;
+
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN;
+import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.ContrastColorUtil;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class provides some methods used by both the {@link ConversationLayout} and
+ * {@link CallLayout} which both use the visual design originally created for conversations in R.
+ */
+public class PeopleHelper {
+
+    private static final float COLOR_SHIFT_AMOUNT = 60;
+    /**
+     * Pattern for filter some ignorable characters.
+     * p{Z} for any kind of whitespace or invisible separator.
+     * p{C} for any kind of punctuation character.
+     */
+    private static final Pattern IGNORABLE_CHAR_PATTERN = Pattern.compile("[\\p{C}\\p{Z}]");
+    private static final Pattern SPECIAL_CHAR_PATTERN =
+            Pattern.compile("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
+
+    private Context mContext;
+    private int mAvatarSize;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mTextPaint = new Paint();
+
+    /**
+     * Call this when the view is inflated to provide a context and initialize the helper
+     */
+    public void init(Context context) {
+        mContext = context;
+        mAvatarSize = context.getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mTextPaint.setAntiAlias(true);
+    }
+
+    /**
+     * A utility for animating CachingIconViews away when hidden.
+     */
+    public void animateViewForceHidden(CachingIconView view, boolean forceHidden) {
+        boolean nowForceHidden = view.willBeForceHidden() || view.isForceHidden();
+        if (forceHidden == nowForceHidden) {
+            // We are either already forceHidden or will be
+            return;
+        }
+        view.animate().cancel();
+        view.setWillBeForceHidden(forceHidden);
+        view.animate()
+                .scaleX(forceHidden ? 0.5f : 1.0f)
+                .scaleY(forceHidden ? 0.5f : 1.0f)
+                .alpha(forceHidden ? 0.0f : 1.0f)
+                .setInterpolator(forceHidden ? ALPHA_OUT : ALPHA_IN)
+                .setDuration(160);
+        if (view.getVisibility() != View.VISIBLE) {
+            view.setForceHidden(forceHidden);
+        } else {
+            view.animate().withEndAction(() -> view.setForceHidden(forceHidden));
+        }
+        view.animate().start();
+    }
+
+    /**
+     * This creates an avatar symbol for the given person or group
+     *
+     * @param name        the name of the person or group
+     * @param symbol      a pre-chosen symbol for the person or group.  See
+     *                    {@link #findNamePrefix(CharSequence, String)} or
+     *                    {@link #findNameSplit(CharSequence)}
+     * @param layoutColor the background color of the layout
+     */
+    @NonNull
+    public Icon createAvatarSymbol(@NonNull CharSequence name, @NonNull String symbol,
+            @ColorInt int layoutColor) {
+        if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol)
+                || SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
+            Icon avatarIcon = Icon.createWithResource(mContext, R.drawable.messaging_user);
+            avatarIcon.setTint(findColor(name, layoutColor));
+            return avatarIcon;
+        } else {
+            Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            float radius = mAvatarSize / 2.0f;
+            int color = findColor(name, layoutColor);
+            mPaint.setColor(color);
+            canvas.drawCircle(radius, radius, radius, mPaint);
+            boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+            mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+            mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
+            int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
+            canvas.drawText(symbol, radius, yPos, mTextPaint);
+            return Icon.createWithBitmap(bitmap);
+        }
+    }
+
+    private int findColor(@NonNull CharSequence senderName, int layoutColor) {
+        double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
+        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+        // we need to offset the range if the luminance is too close to the borders
+        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+        return ContrastColorUtil.getShiftedColor(layoutColor,
+                (int) (shift * COLOR_SHIFT_AMOUNT));
+    }
+
+    /**
+     * Get the name with whitespace and punctuation characters removed
+     */
+    private String getPureName(@NonNull CharSequence name) {
+        return IGNORABLE_CHAR_PATTERN.matcher(name).replaceAll("" /* replacement */);
+    }
+
+    /**
+     * Gets a single character string prefix name for the person or group
+     *
+     * @param name     the name of the person or group
+     * @param fallback the string to return if the name has no usable characters
+     */
+    public String findNamePrefix(@NonNull CharSequence name, String fallback) {
+        String pureName = getPureName(name);
+        if (pureName.isEmpty()) {
+            return fallback;
+        }
+        try {
+            return new String(Character.toChars(pureName.codePointAt(0)));
+        } catch (RuntimeException ignore) {
+            return fallback;
+        }
+    }
+
+    /**
+     * Find a 1 or 2 character prefix name for the person or group
+     */
+    public String findNameSplit(@NonNull CharSequence name) {
+        String nameString = name instanceof String ? ((String) name) : name.toString();
+        String[] split = nameString.trim().split("[ ]+");
+        if (split.length > 1) {
+            String first = findNamePrefix(split[0], null);
+            String second = findNamePrefix(split[1], null);
+            if (first != null && second != null) {
+                return first + second;
+            }
+        }
+        return findNamePrefix(name, "");
+    }
+}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index cb586d6..8982519 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -1236,6 +1236,8 @@
 
         if (IncrementalManager.isFeatureEnabled()) {
             addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
+            addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION,
+                    IncrementalManager.isV2Available() ? 2 : 1);
         }
 
         if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8edc8a1..6983d35 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -184,6 +184,7 @@
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
+                "com_android_internal_os_KernelCpuBpfTracking.cpp",
                 "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
                 "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
                 "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
@@ -207,6 +208,7 @@
             ],
 
             shared_libs: [
+                "android.hardware.memtrack-unstable-ndk_platform",
                 "audioclient-types-aidl-cpp",
                 "audioflinger-aidl-cpp",
                 "av-types-aidl-cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8879111..38bcc0f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -190,6 +190,7 @@
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
 extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
@@ -1586,6 +1587,7 @@
         REG_JNI(register_android_security_Scrypt),
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
+        REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking),
         REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader),
         REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
         REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index f1998a5..c7439f1 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -188,7 +188,7 @@
   ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
 
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  std::unique_ptr<const ApkAssets> apk_assets;
+  std::unique_ptr<ApkAssets> apk_assets;
   switch (format) {
     case FORMAT_APK: {
       auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
@@ -354,7 +354,7 @@
 }
 
 static jlong NativeGetFinalizer(JNIEnv* /*env*/, jclass /*clazz*/) {
-  return reinterpret_cast<jlong>(&NativeDestroy);
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(&NativeDestroy));
 }
 
 static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index bb51c57..8dcb210 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,6 +33,7 @@
 #include <string>
 #include <vector>
 
+#include <aidl/android/hardware/memtrack/DeviceInfo.h>
 #include <android-base/logging.h>
 #include <bionic/malloc.h>
 #include <debuggerd/client.h>
@@ -43,7 +44,9 @@
 #include <nativehelper/JNIPlatformHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
 #include <dmabufinfo/dmabufinfo.h>
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
 #include <meminfo/procmeminfo.h>
 #include <meminfo/sysmeminfo.h>
 #include <memtrack/memtrack.h>
@@ -519,14 +522,15 @@
     }
 
     if (outUssSwapPssRss != NULL) {
-        if (env->GetArrayLength(outUssSwapPssRss) >= 1) {
+        int outLen = env->GetArrayLength(outUssSwapPssRss);
+        if (outLen >= 1) {
             jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0);
             if (outUssSwapPssRssArray != NULL) {
                 outUssSwapPssRssArray[0] = uss;
-                if (env->GetArrayLength(outUssSwapPssRss) >= 2) {
+                if (outLen >= 2) {
                     outUssSwapPssRssArray[1] = swapPss;
                 }
-                if (env->GetArrayLength(outUssSwapPssRss) >= 3) {
+                if (outLen >= 3) {
                     outUssSwapPssRssArray[2] = rss;
                 }
             }
@@ -535,10 +539,20 @@
     }
 
     if (outMemtrack != NULL) {
-        if (env->GetArrayLength(outMemtrack) >= 1) {
+        int outLen = env->GetArrayLength(outMemtrack);
+        if (outLen >= 1) {
             jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
             if (outMemtrackArray != NULL) {
                 outMemtrackArray[0] = memtrack;
+                if (outLen >= 2) {
+                    outMemtrackArray[1] = graphics_mem.graphics;
+                }
+                if (outLen >= 3) {
+                    outMemtrackArray[2] = graphics_mem.gl;
+                }
+                if (outLen >= 4) {
+                    outMemtrackArray[3] = graphics_mem.other;
+                }
             }
             env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
         }
@@ -805,6 +819,26 @@
     return heapsSizeKb;
 }
 
+static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufTotalSizeKb = -1;
+    uint64_t size;
+
+    if (dmabufinfo::GetDmabufTotalExportedKb(&size)) {
+        dmabufTotalSizeKb = size;
+    }
+    return dmabufTotalSizeKb;
+}
+
+static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufHeapTotalSizeKb = -1;
+    uint64_t size;
+
+    if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) {
+        dmabufHeapTotalSizeKb = size;
+    }
+    return dmabufHeapTotalSizeKb;
+}
+
 static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
     jlong poolsSizeKb = -1;
     uint64_t size;
@@ -816,8 +850,44 @@
     return poolsSizeKb;
 }
 
-static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) {
-    jlong ionPss = 0;
+static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject clazz) {
+    jlong poolsSizeKb = -1;
+    uint64_t size;
+
+    if (meminfo::ReadDmabufHeapPoolsSizeKb(&size)) {
+        poolsSizeKb = size;
+    }
+
+    return poolsSizeKb;
+}
+
+static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) {
+    std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info;
+    if (!memtrack_gpu_device_info(&gpu_device_info)) {
+        return -1;
+    }
+
+    dmabufinfo::DmabufSysfsStats stats;
+    if (!GetDmabufSysfsStats(&stats)) {
+        return -1;
+    }
+
+    jlong sizeKb = 0;
+    const auto& importer_stats = stats.importer_info();
+    for (const auto& dev_info : gpu_device_info) {
+        const auto& importer_info = importer_stats.find(dev_info.name);
+        if (importer_info == importer_stats.end()) {
+            continue;
+        }
+
+        sizeKb += importer_info->second.size;
+    }
+
+    return sizeKb;
+}
+
+static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
+    jlong dmabufPss = 0;
     std::vector<dmabufinfo::DmaBuffer> dmabufs;
 
     std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
@@ -841,10 +911,10 @@
     }
 
     for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
-        ionPss += buf.size() / 1024;
+        dmabufPss += buf.size() / 1024;
     }
 
-    return ionPss;
+    return dmabufPss;
 }
 
 static jlong android_os_Debug_getGpuTotalUsageKb(JNIEnv* env, jobject clazz) {
@@ -922,10 +992,18 @@
             (void*)android_os_Debug_getFreeZramKb },
     { "getIonHeapsSizeKb", "()J",
             (void*)android_os_Debug_getIonHeapsSizeKb },
+    { "getDmabufTotalExportedKb", "()J",
+            (void*)android_os_Debug_getDmabufTotalExportedKb },
+    { "getGpuDmaBufUsageKb", "()J",
+            (void*)android_os_Debug_getGpuDmaBufUsageKb },
+    { "getDmabufHeapTotalExportedKb", "()J",
+            (void*)android_os_Debug_getDmabufHeapTotalExportedKb },
     { "getIonPoolsSizeKb", "()J",
             (void*)android_os_Debug_getIonPoolsSizeKb },
-    { "getIonMappedSizeKb", "()J",
-            (void*)android_os_Debug_getIonMappedSizeKb },
+    { "getDmabufMappedSizeKb", "()J",
+            (void*)android_os_Debug_getDmabufMappedSizeKb },
+    { "getDmabufHeapPoolsSizeKb", "()J",
+            (void*)android_os_Debug_getDmabufHeapPoolsSizeKb },
     { "getGpuTotalUsageKb", "()J",
             (void*)android_os_Debug_getGpuTotalUsageKb },
     { "isVmapStack", "()Z",
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index 44bff01..2384efa 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -30,6 +30,10 @@
     return IncFs_IsEnabled();
 }
 
+static jboolean nativeIsV2Available(JNIEnv* env, jobject clazz) {
+    return !!(IncFs_Features() & INCFS_FEATURE_V2);
+}
+
 static jboolean nativeIsIncrementalPath(JNIEnv* env,
                                     jobject clazz,
                                     jstring javaPath) {
@@ -53,12 +57,12 @@
     return result;
 }
 
-static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
-                                               {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
-                                                (void*)nativeIsIncrementalPath},
-                                               {"nativeUnsafeGetFileSignature",
-                                                "(Ljava/lang/String;)[B",
-                                                (void*)nativeUnsafeGetFileSignature}};
+static const JNINativeMethod method_table[] =
+        {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+         {"nativeIsV2Available", "()Z", (void*)nativeIsV2Available},
+         {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
+         {"nativeUnsafeGetFileSignature", "(Ljava/lang/String;)[B",
+          (void*)nativeUnsafeGetFileSignature}};
 
 int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index c08363b..dcfa950 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -377,6 +377,10 @@
     return (int) sp;
 }
 
+jint android_os_Process_createProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) {
+    return createProcessGroup(uid, pid);
+}
+
 /** Sample CPUset list format:
  *  0-3,4,6-8
  */
@@ -1358,6 +1362,7 @@
         {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
         {"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
         {"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
+        {"createProcessGroup", "(II)I", (void*)android_os_Process_createProcessGroup},
         {"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores},
         {"setSwappiness", "(IZ)Z", (void*)android_os_Process_setSwappiness},
         {"setArgV0", "(Ljava/lang/String;)V", (void*)android_os_Process_setArgV0},
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index ac680f6..9746a07 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -199,7 +199,13 @@
     for (;;) {
         uint32_t publishedSeq;
         bool handled;
-        status_t status = mInputPublisher.receiveFinishedSignal(&publishedSeq, &handled);
+        std::function<void(uint32_t seq, bool handled, nsecs_t consumeTime)> callback =
+                [&publishedSeq, &handled](uint32_t inSeq, bool inHandled,
+                                          nsecs_t inConsumeTime) -> void {
+            publishedSeq = inSeq;
+            handled = inHandled;
+        };
+        status_t status = mInputPublisher.receiveFinishedSignal(callback);
         if (status) {
             if (status == WOULD_BLOCK) {
                 return OK;
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 191f748..0aac07d 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -51,19 +51,12 @@
     client->decStrong((void*)nativeCreate);
 }
 
-static void nativeKill(JNIEnv* env, jclass clazz, jlong ptr) {
-    SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
-    client->dispose();
-}
-
 static const JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "nativeCreate", "()J",
             (void*)nativeCreate },
     { "nativeDestroy", "(J)V",
             (void*)nativeDestroy },
-    { "nativeKill", "(J)V",
-            (void*)nativeKill }
 };
 
 int register_android_view_SurfaceSession(JNIEnv* env) {
diff --git a/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
new file mode 100644
index 0000000..e6a82f6
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuBpfTracking.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static jboolean KernelCpuBpfTracking_isSupported(JNIEnv *, jobject) {
+    return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
+}
+
+static const JNINativeMethod methods[] = {
+        {"isSupported", "()Z", (void *)KernelCpuBpfTracking_isSupported},
+};
+
+int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv *env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/KernelCpuBpfTracking", methods,
+                                NELEM(methods));
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
index d8446ca..7249238 100644
--- a/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
+++ b/core/jni/com_android_internal_os_KernelCpuTotalBpfMapReader.cpp
@@ -20,10 +20,6 @@
 
 namespace android {
 
-static jboolean KernelCpuTotalBpfMapReader_isSupported(JNIEnv *, jobject) {
-    return android::bpf::isTrackingUidTimesSupported() ? JNI_TRUE : JNI_FALSE;
-}
-
 static jboolean KernelCpuTotalBpfMapReader_read(JNIEnv *env, jobject, jobject callback) {
     jclass callbackClass = env->GetObjectClass(callback);
     jmethodID callbackMethod = env->GetMethodID(callbackClass, "accept", "(IIJ)V");
@@ -51,7 +47,6 @@
 static const JNINativeMethod methods[] = {
         {"read", "(Lcom/android/internal/os/KernelCpuTotalBpfMapReader$Callback;)Z",
          (void *)KernelCpuTotalBpfMapReader_read},
-        {"isSupported", "()Z", (void *)KernelCpuTotalBpfMapReader_isSupported},
 };
 
 int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv *env) {
diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
index 6794c8c..0041f19 100644
--- a/core/proto/android/net/networkrequest.proto
+++ b/core/proto/android/net/networkrequest.proto
@@ -63,6 +63,9 @@
         // higher-scoring network will not go into the background immediately,
         // but will linger and go into the background after the linger timeout.
         TYPE_BACKGROUND_REQUEST = 5;
+        // Like TRACK_DEFAULT, but tracks the system default network, instead of
+        // the default network of the calling application.
+        TYPE_TRACK_SYSTEM_DEFAULT = 6;
     }
     // The type of the request. This is only used by the system and is always
     // NONE elsewhere.
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
new file mode 100644
index 0000000..aab054f
--- /dev/null
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.server.vibrator;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message OneShotProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 duration = 1;
+    repeated int32 amplitude = 2;
+}
+
+message WaveformProto {
+   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+   repeated int32 timings = 1;
+   repeated int32 amplitudes = 2;
+   required bool repeat = 3;
+}
+
+message PrebakedProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 effect_id = 1;
+    optional int32 effect_strength = 2;
+    optional int32 fallback = 3;
+}
+
+message ComposedProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 effect_ids = 1;
+    repeated float effect_scales = 2;
+    repeated int32 delays = 3;
+}
+
+// A com.android.os.VibrationEffect object.
+message VibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional OneShotProto oneshot = 1;
+    optional WaveformProto waveform = 2;
+    optional PrebakedProto prebaked = 3;
+    optional ComposedProto composed = 4;
+}
+
+message SyncVibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated VibrationEffectProto effects = 1;
+    repeated int32 vibrator_ids = 2;
+}
+
+// A com.android.os.CombinedVibrationEffect object.
+message CombinedVibrationEffectProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated SyncVibrationEffectProto effects = 1;
+    repeated int32 delays = 2;
+}
+
+message VibrationAttributesProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 usage = 1;
+    optional int32 audio_usage = 2;
+    optional int32 flags = 3;
+}
+
+// Next id: 7
+message VibrationProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int64 start_time = 1;
+    optional int64 end_time = 2;
+    optional CombinedVibrationEffectProto effect = 3;
+    optional CombinedVibrationEffectProto original_effect = 4;
+    optional VibrationAttributesProto attributes = 5;
+    optional int32 status = 6;
+}
+
+// Next id: 18
+message VibratorManagerServiceDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 vibrator_ids = 1;
+    optional VibrationProto current_vibration = 2;
+    optional bool is_vibrating = 3;
+    optional VibrationProto current_external_vibration = 4;
+    optional bool vibrator_under_external_control = 5;
+    optional bool low_power_mode = 6;
+    optional int32 haptic_feedback_intensity = 7;
+    optional int32 haptic_feedback_default_intensity = 8;
+    optional int32 notification_intensity = 9;
+    optional int32 notification_default_intensity = 10;
+    optional int32 ring_intensity = 11;
+    optional int32 ring_default_intensity = 12;
+    repeated VibrationProto previous_ring_vibrations = 13;
+    repeated VibrationProto previous_notification_vibrations = 14;
+    repeated VibrationProto previous_alarm_vibrations = 15;
+    repeated VibrationProto previous_vibrations = 16;
+    repeated VibrationProto previous_external_vibrations = 17;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
deleted file mode 100644
index 9e42e9e..0000000
--- a/core/proto/android/server/vibratorservice.proto
+++ /dev/null
@@ -1,97 +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.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-option java_multiple_files = true;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-
-message OneShotProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 duration = 1;
-    repeated int32 amplitude = 2;
-}
-
-message WaveformProto {
-   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-   repeated int32 timings = 1;
-   repeated int32 amplitudes = 2;
-   required bool repeat = 3;
-}
-
-message PrebakedProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int32 effect_id = 1;
-    optional int32 effect_strength = 2;
-    optional int32 fallback = 3;
-}
-
-message ComposedProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    repeated int32 effect_ids = 1;
-    repeated float effect_scales = 2;
-    repeated int32 delays = 3;
-}
-
-// A com.android.os.VibrationEffect object.
-message VibrationEffectProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional OneShotProto oneshot = 3;
-    optional WaveformProto waveform = 1;
-    optional PrebakedProto prebaked = 2;
-    optional ComposedProto composed = 4;
-}
-
-message VibrationAttributesProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int32 usage = 1;
-    optional int32 audio_usage = 2;
-    optional int32 flags = 3;
-}
-
-// Next id: 7
-message VibrationProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional int64 start_time = 1;
-    optional int64 end_time = 4;
-    optional VibrationEffectProto effect = 2;
-    optional VibrationEffectProto original_effect = 3;
-    optional VibrationAttributesProto attributes = 5;
-    optional int32 status = 6;
-}
-
-// Next id: 17
-message VibratorServiceDumpProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-    optional VibrationProto current_vibration = 1;
-    optional bool is_vibrating = 2;
-    optional VibrationProto current_external_vibration = 3;
-    optional bool vibrator_under_external_control = 4;
-    optional bool low_power_mode = 5;
-    optional int32 haptic_feedback_intensity = 6;
-    optional int32 haptic_feedback_default_intensity = 14;
-    optional int32 notification_intensity = 7;
-    optional int32 notification_default_intensity = 15;
-    optional int32 ring_intensity = 8;
-    optional int32 ring_default_intensity = 16;
-    repeated VibrationProto previous_ring_vibrations = 9;
-    repeated VibrationProto previous_notification_vibrations = 10;
-    repeated VibrationProto previous_alarm_vibrations = 11;
-    repeated VibrationProto previous_vibrations = 12;
-    repeated VibrationProto previous_external_vibrations = 13;
-}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 856657a..fbc1d4f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1055,6 +1055,14 @@
         android:description="@string/permdesc_accessImsCallService"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+         Only granted if the application is a system app AND is in the Default SMS Role.
+         The permission is revoked when the app is taken out of the Default SMS Role.
+        <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to read the user's call log.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #READ_CONTACTS} permission and <em>both</em> your <a
@@ -1862,6 +1870,12 @@
     <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
+         preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
+         <p>Not for use by third-party or privileged applications. -->
+    <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -3994,6 +4008,11 @@
     <permission android:name="android.permission.MOVE_PACKAGE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @TestApi Allows an application to keep uninstalled packages as apks.
+         @hide -->
+    <permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to change whether an application component (other than its own) is
          enabled or not.
          <p>Not for use by third-party applications. -->
@@ -4353,6 +4372,12 @@
     <permission android:name="android.permission.POWER_SAVER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows providing the system with battery predictions.
+         Superseded by DEVICE_POWER permission. @hide @SystemApi
+    -->
+    <permission android:name="android.permission.BATTERY_PREDICTION"
+        android:protectionLevel="signature|privileged" />
+
    <!-- Allows access to the PowerManager.userActivity function.
    <p>Not for use by third-party applications. @hide @SystemApi -->
     <permission android:name="android.permission.USER_ACTIVITY"
@@ -5452,6 +5477,16 @@
     <permission android:name="android.permission.MANAGE_GAME_MODE"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+         when they are performing reboot-blocking work.
+         @hide -->
+    <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
+    <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
+        android:protectionLevel="signature|recents" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/color/btn_leanback_color.xml b/core/res/res/color/btn_leanback_color.xml
new file mode 100644
index 0000000..012df60
--- /dev/null
+++ b/core/res/res/color/btn_leanback_color.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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+        android:color="@color/btn_leanback_focused" />
+    <item android:state_enabled="false"
+        android:color="@android:color/transparent" />
+    <item android:color="@color/btn_leanback_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/btn_leanback_text_color.xml b/core/res/res/color/btn_leanback_text_color.xml
new file mode 100644
index 0000000..df0df94
--- /dev/null
+++ b/core/res/res/color/btn_leanback_text_color.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/btn_text_leanback_focused" android:state_focused="true"/>
+    <item android:color="@color/btn_text_leanback_unfocused"/>
+</selector>
diff --git a/core/res/res/drawable/btn_borderless_material.xml b/core/res/res/drawable/btn_borderless_material.xml
index 08e1060..1a0912e 100644
--- a/core/res/res/drawable/btn_borderless_material.xml
+++ b/core/res/res/drawable/btn_borderless_material.xml
@@ -15,7 +15,8 @@
 -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?attr/colorControlHighlight">
+        android:color="?attr/colorControlHighlight"
+        android:rippleStyle="?attr/rippleStyle">
     <item android:id="@id/mask"
           android:drawable="@drawable/btn_default_mtrl_shape" />
 </ripple>
diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml
index 7ba21e8..5274ef2 100644
--- a/core/res/res/drawable/btn_colored_material.xml
+++ b/core/res/res/drawable/btn_colored_material.xml
@@ -19,7 +19,8 @@
        android:insetTop="@dimen/button_inset_vertical_material"
        android:insetRight="@dimen/button_inset_horizontal_material"
        android:insetBottom="@dimen/button_inset_vertical_material">
-    <ripple android:color="?attr/colorControlHighlight">
+    <ripple android:color="?attr/colorControlHighlight"
+            android:rippleStyle="?attr/rippleStyle">
         <item>
             <shape android:shape="rectangle"
                    android:tint="@color/btn_colored_background_material">
diff --git a/core/res/res/drawable/btn_default_material.xml b/core/res/res/drawable/btn_default_material.xml
index ed2b5aa..6a9e621 100644
--- a/core/res/res/drawable/btn_default_material.xml
+++ b/core/res/res/drawable/btn_default_material.xml
@@ -15,6 +15,7 @@
 -->
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?attr/colorControlHighlight">
+        android:color="?attr/colorControlHighlight"
+        android:rippleStyle="?attr/rippleStyle">
     <item android:drawable="@drawable/btn_default_mtrl_shape" />
 </ripple>
diff --git a/core/res/res/drawable/btn_leanback.xml b/core/res/res/drawable/btn_leanback.xml
new file mode 100644
index 0000000..9a63986
--- /dev/null
+++ b/core/res/res/drawable/btn_leanback.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/btn_leanback_color"/>
+    <corners android:radius="@dimen/leanback_button_radius"/>
+</shape>
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 1a574fe..ad68054 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -23,7 +23,7 @@
     <ripple android:color="?attr/colorControlHighlight">
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?attr/buttonCornerRadius" />
+                <corners android:radius="@dimen/notification_action_button_radius" />
                 <padding android:left="@dimen/button_padding_horizontal_material"
                          android:top="@dimen/button_padding_vertical_material"
                          android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/drawable/btn_toggle_material.xml b/core/res/res/drawable/btn_toggle_material.xml
index 8b19e4a..7cee382 100644
--- a/core/res/res/drawable/btn_toggle_material.xml
+++ b/core/res/res/drawable/btn_toggle_material.xml
@@ -21,7 +21,8 @@
        android:insetBottom="@dimen/button_inset_vertical_material">
     <layer-list android:paddingMode="stack">
         <item>
-            <ripple android:color="?attr/colorControlHighlight">
+            <ripple android:color="?attr/colorControlHighlight"
+                android:rippleStyle="?attr/rippleStyle">
                 <item>
                     <shape android:shape="rectangle"
                            android:tint="?attr/colorButtonNormal">
diff --git a/core/res/res/drawable/ic_call_answer.xml b/core/res/res/drawable/ic_call_answer.xml
new file mode 100644
index 0000000..77c0ad1
--- /dev/null
+++ b/core/res/res/drawable/ic_call_answer.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M6.0168,3.3333L6.5751,5.575L4.6668,7.4916C3.8751,5.7166 3.6001,4.1916
+            3.4834,3.3333H6.0168ZM14.4251,13.375L16.6668,13.9416V16.5166C15.8084,16.4 14.2668,16.125
+            12.4834,15.325L14.4251,13.375ZM6.3418,1.6666H2.5668C2.0918,1.6666 1.7001,2.0666
+            1.7334,2.5416C2.4834,12.875 11.7668,18.275 17.5168,18.275C17.9668,18.275 18.3334,17.9
+            18.3334,17.4416V13.6166C18.3334,13.0416 17.9418,12.5416
+            17.3834,12.4083L14.5918,11.7083C14.2251,11.6166 13.7584,11.6833
+            13.4084,12.0333L10.9251,14.5166C8.6751,13.1833 6.7918,11.3
+            5.4668,9.0416L7.9168,6.5916C8.2251,6.2833 8.3501,5.8333
+            8.2418,5.4083L7.5584,2.6166C7.4168,2.0583 6.9168,1.6666 6.3418,1.6666Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_call_decline.xml b/core/res/res/drawable/ic_call_decline.xml
new file mode 100644
index 0000000..a5ee8f4
--- /dev/null
+++ b/core/res/res/drawable/ic_call_decline.xml
@@ -0,0 +1,36 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M5.2834,9.3083V10.7833L4.1084,11.4916L3.1334,10.5166C3.8084,10.0416
+            4.5251,9.6333 5.2834,9.3083ZM14.75,9.325C15.4917,9.65 16.2084,10.05
+            16.875,10.525L15.925,11.475L14.75,10.7666V9.325ZM9.975,6.6666C6.8584,6.6666 3.725,7.7333
+            1.1751,9.9416C0.8084,10.2583 0.9667,10.7166 1.1501,10.9L3.2917,13.0416C3.4917,13.2333
+            3.7417,13.3333 4.0001,13.3333C4.175,13.3333 4.35,13.2833
+            4.5084,13.1916L6.4667,12.0166C6.725,11.8583 6.95,11.5583 6.95,11.1666V8.3833C7.95,8.125
+            8.975,8 10.0084,8C11.0417,8 12.075,8.1333 13.0834,8.3916V11.1416C13.0834,11.4916
+            13.2667,11.8166 13.5667,11.9916L15.525,13.1666C15.6834,13.2583 15.8584,13.3083
+            16.0334,13.3083C16.2917,13.3083 16.5417,13.2083
+            16.7334,13.0166L18.85,10.9C19.1167,10.6333 19.1167,10.1916 18.825,9.9416C16.3334,7.7833
+            13.1667,6.6666 9.975,6.6666Z"/>
+</vector>
diff --git a/core/res/res/layout/alert_dialog_button_bar_leanback.xml b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..ea94af6
--- /dev/null
+++ b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?attr/buttonBarStyle">
+    <com.android.internal.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:gravity="start">
+
+        <Button
+            android:id="@+id/button1"
+            style="?attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button2"
+            style="?attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <Button
+            android:id="@+id/button3"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
diff --git a/core/res/res/layout/alert_dialog_leanback.xml b/core/res/res/layout/alert_dialog_leanback.xml
index 848015c..091613f 100644
--- a/core/res/res/layout/alert_dialog_leanback.xml
+++ b/core/res/res/layout/alert_dialog_leanback.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2014 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.
@@ -15,108 +15,70 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<com.android.internal.widget.AlertDialogLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/parentPanel"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:background="@drawable/dialog_background_material"
-    android:translationZ="@dimen/floating_window_z"
-    android:layout_marginLeft="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginTop="@dimen/leanback_alert_dialog_vertical_margin"
-    android:layout_marginRight="@dimen/leanback_alert_dialog_horizontal_margin"
-    android:layout_marginBottom="@dimen/leanback_alert_dialog_vertical_margin">
+    android:gravity="start|top"
+    android:orientation="vertical">
 
-    <LinearLayout android:id="@+id/topPanel"
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical">
-        <LinearLayout android:id="@+id/title_template"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:gravity="center_vertical|start"
-            android:paddingStart="16dip"
-            android:paddingEnd="16dip"
-            android:paddingTop="16dip">
-            <ImageView android:id="@+id/icon"
-                android:layout_width="32dip"
-                android:layout_height="32dip"
-                android:layout_marginEnd="8dip"
-                android:scaleType="fitCenter"
-                android:src="@null" />
-            <TextView android:id="@+id/alertTitle"
-                style="?attr/windowTitleStyle"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:textAlignment="viewStart" />
-        </LinearLayout>
-        <!-- If the client uses a customTitle, it will be added here. -->
-    </LinearLayout>
+        android:minHeight="48dp">
 
-    <LinearLayout android:id="@+id/contentPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:minHeight="64dp">
-        <ScrollView android:id="@+id/scrollView"
+        <ScrollView
+            android:id="@+id/scrollView"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:clipToPadding="false">
-            <TextView android:id="@+id/message"
-                style="?attr/textAppearanceMedium"
+
+            <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:paddingStart="16dip"
-                android:paddingEnd="16dip"
-                android:paddingTop="16dip" />
-        </ScrollView>
-    </LinearLayout>
+                android:orientation="vertical">
 
-    <FrameLayout android:id="@+id/customPanel"
+                <Space
+                    android:id="@+id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="?attr/dialogPreferredPadding"
+                    android:paddingStart="?attr/dialogPreferredPadding"
+                    style="@style/TextAppearance.Material.Subhead" />
+
+                <Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/customPanel"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:minHeight="64dp">
-        <FrameLayout android:id="@+android:id/custom"
+        android:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
     </FrameLayout>
 
-    <LinearLayout android:id="@+id/buttonPanel"
+    <include
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="@dimen/alert_dialog_button_bar_height"
-        android:orientation="vertical"
-        android:gravity="end"
-        android:padding="16dip">
-        <LinearLayout
-            style="?attr/buttonBarStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layoutDirection="locale">
-            <Button android:id="@+id/button3"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-            <Button android:id="@+id/button2"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-            <Button android:id="@+id/button1"
-                style="?attr/buttonBarButtonStyle"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxLines="2"
-                android:minHeight="@dimen/alert_dialog_button_bar_height" />
-        </LinearLayout>
-     </LinearLayout>
-</LinearLayout>
+        layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.internal.widget.AlertDialogLayout>
diff --git a/core/res/res/layout/notification_material_action_emphasized.xml b/core/res/res/layout/notification_material_action_emphasized.xml
index a6b7b38..cd1f1ab 100644
--- a/core/res/res/layout/notification_material_action_emphasized.xml
+++ b/core/res/res/layout/notification_material_action_emphasized.xml
@@ -22,6 +22,7 @@
     android:layout_height="match_parent"
     android:layout_marginStart="12dp"
     android:layout_weight="1"
+    android:drawablePadding="6dp"
     android:gravity="center"
     android:textColor="@color/notification_default_color"
     android:singleLine="true"
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
new file mode 100644
index 0000000..b018676
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -0,0 +1,166 @@
+<?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
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_header"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingTop="16dp"
+    >
+
+    <TextView
+        android:id="@+id/conversation_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+        android:textSize="16sp"
+        android:singleLine="true"
+        android:layout_weight="1"
+        />
+
+    <TextView
+        android:id="@+id/app_name_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        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"
+        />
+
+    <!-- App Name -->
+    <com.android.internal.widget.ObservableTextView
+        android:id="@+id/app_name_text"
+        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"
+        />
+
+    <TextView
+        android:id="@+id/time_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        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"
+        />
+
+    <DateTimeView
+        android:id="@+id/time"
+        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"
+        />
+
+    <ViewStub
+        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"
+        />
+
+    <ImageView
+        android:id="@+id/verification_icon"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_gravity="center"
+        android:layout_marginStart="4dp"
+        android:contentDescription="@string/notification_alerted_content_description"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/verification_text"
+        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"
+        />
+
+    <ImageButton
+        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:scaleType="fitCenter"
+        android:src="@drawable/ic_feedback_indicator"
+        android:visibility="gone"
+        />
+
+    <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:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
+        />
+
+    <ImageView
+        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:contentDescription="@string/notification_alerted_content_description"
+        android:paddingTop="2dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
new file mode 100644
index 0000000..e9ec7ce
--- /dev/null
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_icon_container"
+    android:layout_width="@dimen/conversation_content_start"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingTop="12dp"
+    android:paddingBottom="12dp"
+    android:importantForAccessibility="no"
+    >
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_gravity="top|center_horizontal"
+        >
+
+        <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
+        <com.android.internal.widget.CachingIconView
+            android:id="@+id/conversation_icon"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:scaleType="centerCrop"
+            android:importantForAccessibility="no"
+            />
+
+        <ViewStub
+            android:layout="@layout/conversation_face_pile_layout"
+            android:layout_width="@dimen/conversation_avatar_size"
+            android:layout_height="@dimen/conversation_avatar_size"
+            android:id="@+id/conversation_face_pile"
+            />
+
+        <FrameLayout
+            android:id="@+id/conversation_icon_badge"
+            android:layout_width="@dimen/conversation_icon_size_badged"
+            android:layout_height="@dimen/conversation_icon_size_badged"
+            android:layout_marginLeft="@dimen/conversation_badge_side_margin"
+            android:layout_marginTop="@dimen/conversation_badge_side_margin"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            >
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_background"
+                android:forceHasOverlappingRendering="false"
+                android:scaleType="center"
+                />
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/icon"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_margin="4dp"
+                android:layout_gravity="center"
+                android:forceHasOverlappingRendering="false"
+                />
+
+            <com.android.internal.widget.CachingIconView
+                android:id="@+id/conversation_icon_badge_ring"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:src="@drawable/conversation_badge_ring"
+                android:visibility="gone"
+                android:forceHasOverlappingRendering="false"
+                android:clipToPadding="false"
+                android:scaleType="center"
+                />
+        </FrameLayout>
+    </FrameLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
new file mode 100644
index 0000000..471d874
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -0,0 +1,92 @@
+<?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
+  -->
+<com.android.internal.widget.CallLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:tag="call"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+    <include layout="@layout/notification_template_conversation_icon_container" />
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_action_list_height"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="top"
+            android:orientation="horizontal"
+            >
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginStart="@dimen/conversation_content_start"
+                android:layout_marginEnd="@dimen/notification_content_margin_end"
+                android:orientation="vertical"
+                android:minHeight="68dp"
+                >
+
+                <include
+                    layout="@layout/notification_template_conversation_header"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+
+                <include layout="@layout/notification_template_text" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    layout="@layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <!-- TODO(b/179178086): remove padding from main column when this is visible -->
+            <com.android.internal.widget.NotificationExpandButton
+                android:id="@+id/expand_button"
+                android:layout_width="@dimen/notification_header_expand_icon_size"
+                android:layout_height="@dimen/notification_header_expand_icon_size"
+                android:layout_gravity="top|end"
+                android:contentDescription="@string/expand_button_content_description_collapsed"
+                android:paddingTop="@dimen/notification_expand_button_padding_top"
+                android:scaleType="center"
+                android:visibility="gone"
+                />
+
+        </LinearLayout>
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index f9364d5..f3aa540 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -24,82 +24,7 @@
     android:theme="@style/Theme.DeviceDefault.Notification"
     >
 
-    <FrameLayout
-        android:id="@+id/conversation_icon_container"
-        android:layout_width="@dimen/conversation_content_start"
-        android:layout_height="wrap_content"
-        android:gravity="start|top"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:paddingTop="12dp"
-        android:paddingBottom="12dp"
-        android:importantForAccessibility="no"
-    >
-
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:layout_gravity="top|center_horizontal"
-        >
-
-            <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
-            <com.android.internal.widget.CachingIconView
-                android:id="@+id/conversation_icon"
-                android:layout_width="@dimen/conversation_avatar_size"
-                android:layout_height="@dimen/conversation_avatar_size"
-                android:scaleType="centerCrop"
-                android:importantForAccessibility="no"
-            />
-
-            <ViewStub
-                android:layout="@layout/conversation_face_pile_layout"
-                android:layout_width="@dimen/conversation_avatar_size"
-                android:layout_height="@dimen/conversation_avatar_size"
-                android:id="@+id/conversation_face_pile"
-                />
-
-            <FrameLayout
-                android:id="@+id/conversation_icon_badge"
-                android:layout_width="@dimen/conversation_icon_size_badged"
-                android:layout_height="@dimen/conversation_icon_size_badged"
-                android:layout_marginLeft="@dimen/conversation_badge_side_margin"
-                android:layout_marginTop="@dimen/conversation_badge_side_margin"
-                android:clipChildren="false"
-                android:clipToPadding="false"
-            >
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/conversation_icon_badge_bg"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_gravity="center"
-                    android:src="@drawable/conversation_badge_background"
-                    android:forceHasOverlappingRendering="false"
-                    android:scaleType="center"
-                />
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/icon"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:layout_margin="4dp"
-                    android:layout_gravity="center"
-                    android:forceHasOverlappingRendering="false"
-                />
-                <com.android.internal.widget.CachingIconView
-                    android:id="@+id/conversation_icon_badge_ring"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center"
-                    android:src="@drawable/conversation_badge_ring"
-                    android:visibility="gone"
-                    android:forceHasOverlappingRendering="false"
-                    android:clipToPadding="false"
-                    android:scaleType="center"
-                />
-            </FrameLayout>
-        </FrameLayout>
-    </FrameLayout>
+    <include layout="@layout/notification_template_conversation_icon_container" />
 
     <!-- Wraps entire "expandable" notification -->
     <com.android.internal.widget.RemeasuringLinearLayout
@@ -132,161 +57,14 @@
 
                 <!-- Use layout_marginStart instead of paddingStart to work around strange
                      measurement behavior on lower display densities. -->
-                <LinearLayout
-                    android:id="@+id/conversation_header"
+                <include
+                    layout="@layout/notification_template_conversation_header"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:orientation="horizontal"
-                    android:paddingTop="16dp"
                     android:layout_marginBottom="2dp"
                     android:layout_marginStart="@dimen/conversation_content_start"
-                >
-                    <TextView
-                        android:id="@+id/conversation_text"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
-                        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
-                        android:textSize="16sp"
-                        android:singleLine="true"
-                        android:layout_weight="1"
-                        />
-
-                    <TextView
-                        android:id="@+id/app_name_divider"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        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"
                     />
 
-                    <!-- App Name -->
-                    <com.android.internal.widget.ObservableTextView
-                        android:id="@+id/app_name_text"
-                        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"
-                    />
-
-                    <TextView
-                        android:id="@+id/time_divider"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textAppearance="?attr/notificationHeaderTextAppearance"
-                        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"
-                    />
-
-                    <DateTimeView
-                        android:id="@+id/time"
-                        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"
-                    />
-
-                    <ImageButton
-                        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:scaleType="fitCenter"
-                        android:src="@drawable/ic_feedback_indicator"
-                        android:visibility="gone"
-                        />
-
-                    <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:scaleType="fitCenter"
-                        android:visibility="gone"
-                        android:contentDescription="@string/notification_work_profile_content_description"
-                        />
-
-                    <ImageView
-                        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:contentDescription="@string/notification_alerted_content_description"
-                        android:paddingTop="2dp"
-                        android:scaleType="fitCenter"
-                        android:src="@drawable/ic_notifications_alerted"
-                        android:visibility="gone"
-                        />
-
-                    <LinearLayout
-                        android:id="@+id/app_ops"
-                        android:layout_height="wrap_content"
-                        android:layout_width="wrap_content"
-                        android:paddingTop="3dp"
-                        android:layout_marginStart="2dp"
-                        android:background="?android:selectableItemBackgroundBorderless"
-                        android:orientation="horizontal" >
-                        <ImageView
-                            android:layout_marginStart="4dp"
-                            android:id="@+id/camera"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_camera"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_camera_active"
-                            />
-                        <ImageView
-                            android:id="@+id/mic"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_mic"
-                            android:layout_marginStart="4dp"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_microphone_active"
-                            />
-                        <ImageView
-                            android:id="@+id/overlay"
-                            android:layout_width="?attr/notificationHeaderIconSize"
-                            android:layout_height="?attr/notificationHeaderIconSize"
-                            android:src="@drawable/ic_alert_window_layer"
-                            android:layout_marginStart="4dp"
-                            android:visibility="gone"
-                            android:focusable="false"
-                            android:contentDescription="@string/notification_appops_overlay_active"
-                            />
-                    </LinearLayout>
-                </LinearLayout>
-
                 <!-- Messages -->
                 <com.android.internal.widget.MessagingLinearLayout
                     android:id="@+id/notification_messaging"
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 7cda03f..7656dd5 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -26,7 +26,7 @@
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:singleLine="true"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:visibility="?attr/notificationHeaderAppNameVisibility"
         />
 
@@ -34,7 +34,7 @@
         android:id="@+id/header_text_secondary_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
@@ -45,7 +45,7 @@
         android:id="@+id/header_text_secondary"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:visibility="gone"
@@ -56,7 +56,7 @@
         android:id="@+id/header_text_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
@@ -67,7 +67,7 @@
         android:id="@+id/header_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:visibility="gone"
@@ -78,7 +78,7 @@
         android:id="@+id/time_divider"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
         android:layout_marginStart="@dimen/notification_header_separating_margin"
         android:layout_marginEnd="@dimen/notification_header_separating_margin"
         android:text="@string/notification_header_divider_symbol"
diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml
new file mode 100644
index 0000000..513da5e
--- /dev/null
+++ b/core/res/res/layout/splash_screen_view.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<android.window.SplashScreenView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical">
+
+    <View android:id="@+id/splashscreen_icon_view"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:layout_gravity="center"/>
+
+    <View android:id="@+id/splashscreen_branding_view"
+          android:layout_height="wrap_content"
+          android:layout_width="wrap_content"
+          android:layout_gravity="center_horizontal|bottom"
+          android:layout_marginBottom="60dp"/>
+
+</android.window.SplashScreenView>
\ No newline at end of file
diff --git a/core/res/res/values-television/styles_device_defaults.xml b/core/res/res/values-television/styles_device_defaults.xml
new file mode 100644
index 0000000..ef55fc6
--- /dev/null
+++ b/core/res/res/values-television/styles_device_defaults.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>
+    <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog"
+           parent="Widget.Leanback.Button.ButtonBar" />
+</resources>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e567c3d..67b810e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -362,6 +362,12 @@
              surface when the app has not drawn any content into this area. One example is
              when the user is resizing a window of an activity in multi-window mode. -->
         <attr name="windowBackgroundFallback" format="reference|color" />
+        <!-- Blur the screen behind the window with the bounds of the window.
+             The radius defines the size of the neighbouring area, from which pixels will be
+             averaged to form the final color for each pixel in the region.
+             A radius of 0 means no blur. The higher the radius, the denser the blur.
+             Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. -->
+        <attr name="windowBackgroundBlurRadius" format="dimension" />
         <!-- Drawable to use as a frame around the window. -->
         <attr name="windowFrame" format="reference" />
         <!-- Flag indicating whether there should be no title on this window. -->
@@ -1140,6 +1146,15 @@
         <!-- The color applied to the edge effect on scrolling containers. -->
         <attr name="colorEdgeEffect" format="color" />
 
+        <!-- The type of the edge effect. The default is glow. -->
+        <attr name="edgeEffectType">
+            <!-- Use a colored glow at the edge. -->
+            <enum name="glow" value="0" />
+
+            <!-- Stretch the content. -->
+            <enum name="stretch" value="1" />
+        </attr>
+
         <!-- =================== -->
         <!-- Lighting properties -->
         <!-- =================== -->
@@ -1957,6 +1972,7 @@
     <declare-styleable name="Window">
         <attr name="windowBackground" />
         <attr name="windowBackgroundFallback" />
+        <attr name="windowBackgroundBlurRadius" />
         <attr name="windowContentOverlay" />
         <attr name="windowFrame" />
         <attr name="windowNoTitle" />
@@ -2246,6 +2262,23 @@
             -->
             <enum name="always" value="3" />
         </attr>
+
+        <!-- The background color for the splash screen, if not specify then system will
+             calculate from windowBackground. -->
+        <attr name="windowSplashScreenBackground" format="color"/>
+
+        <!-- Replace an icon in the center of the starting window, if the object is animated
+             and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also
+             play the animation while showing the starting window. -->
+        <attr name="windowSplashScreenAnimatedIcon" format="reference"/>
+        <!-- The duration, in milliseconds, of the window splash screen icon animation duration
+             when playing the splash screen starting window. The maximum animation duration should
+             be limited below 1000ms. -->
+        <attr name="windowSplashScreenAnimationDuration" format="integer"/>
+
+        <!-- Place an drawable image in the bottom of the starting window, it can be used to
+             represent the branding of the application. -->
+        <attr name="windowSplashScreenBrandingImage" format="reference"/>
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -3251,6 +3284,16 @@
              a value of 'true' will not override any 'false' value in its parent chain nor will
              it prevent any 'false' in any of its children. -->
         <attr name="forceDarkAllowed" format="boolean" />
+
+        <!-- <p>Whether the View's Outline should be used to clip the contents of the View.
+             <p>Only a single non-rectangular clip can be applied on a View at any time. Circular
+             clips from a
+             {@link android.view.ViewAnimationUtils#createCircularReveal(View, int, int, float,
+             float)} circular reveal animation take priority over Outline clipping, and child
+             Outline clipping takes priority over Outline clipping done by a parent.
+             <p>Note that this flag will only be respected if the View's Outline returns true from
+             {@link android.graphics.Outline#canClip()}. -->
+        <attr name="clipToOutline" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -6346,6 +6389,13 @@
         <!-- The radius of the ripple when fully expanded. By default, the
              radius is computed based on the size of the ripple's container. -->
         <attr name="radius" />
+        <!-- The style of the ripple drawable is solid by default -->
+        <attr name="rippleStyle">
+            <!-- Solid is the default style -->
+            <enum name="solid" value="0" />
+            <!-- Patterned style-->
+            <enum name="patterned" value="1" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="ScaleDrawable">
@@ -9040,6 +9090,7 @@
     <!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
     <declare-styleable name="EdgeEffect">
         <attr name="colorEdgeEffect" />
+        <attr name="edgeEffectType" />
     </declare-styleable>
 
     <!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a
@@ -9233,6 +9284,7 @@
         <attr name="shortcutShortLabel" format="reference" />
         <attr name="shortcutLongLabel" format="reference" />
         <attr name="shortcutDisabledMessage" format="reference" />
+        <attr name="splashScreenTheme" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="ShortcutCategories">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b4e580a..0ae6a76 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -311,6 +311,10 @@
         <flag name="recents" value="0x2000000" />
         <!-- Additional flag from base permission type: this permission is managed by role. -->
         <flag name="role" value="0x4000000" />
+        <!-- Additional flag from base permission type: this permission can also be granted if the
+             requesting application is signed by, or has in its signing lineage, any of the
+             certificate digests declared in {@link android.R.attr#knownCerts}. -->
+        <flag name="knownSigner" value="0x8000000" />
     </attr>
 
     <!-- Flags indicating more context for a permission group. -->
@@ -364,6 +368,15 @@
          {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->
     <attr name="permissionGroup" format="string" />
 
+    <!-- A reference to an array resource containing the signing certificate digests to be granted
+         this permission when using the {@code knownSigner} protection flag. The digest should
+         be computed over the DER encoding of the trusted certificate using the SHA-256 digest
+         algorithm.
+         <p>
+         If only a single signer is declared this can also be a string resource, or the digest
+         can be declared inline as the value for this attribute. -->
+    <attr name="knownCerts" format="reference|string" />
+
     <!-- Specify the name of a user ID that will be shared between multiple
          packages.  By default, each package gets its own unique user-id.
          By setting this value on two or more packages, each of these packages
@@ -1935,6 +1948,7 @@
         <attr name="request" />
         <attr name="protectionLevel" />
         <attr name="permissionFlags" />
+        <attr name="knownCerts" />
     </declare-styleable>
 
     <!-- The <code>permission-group</code> tag declares a logical grouping of
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 20a5d37..5546621 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -153,6 +153,11 @@
 
     <color name="notification_action_list_background_color">@null</color>
 
+    <!-- The color of the Decline and Hang Up actions on a CallStyle notification -->
+    <color name="call_notification_decline_color">#d93025</color>
+    <!-- The color of the Answer action on a CallStyle notification -->
+    <color name="call_notification_answer_color">#1e8e3e</color>
+
     <!-- Keyguard colors -->
     <color name="keyguard_avatar_frame_color">#ffffffff</color>
     <color name="keyguard_avatar_frame_shadow_color">#80000000</color>
diff --git a/core/res/res/values/colors_leanback.xml b/core/res/res/values/colors_leanback.xml
index e52a861e..df0be03 100644
--- a/core/res/res/values/colors_leanback.xml
+++ b/core/res/res/values/colors_leanback.xml
@@ -26,4 +26,9 @@
     <color name="secondary_text_leanback_light">#99222222</color>
 
     <color name="primary_text_leanback_formwizard_default_dark">#ffeeeeee</color>
+
+    <color name="btn_leanback_focused">#E8EAED</color>
+    <color name="btn_leanback_unfocused">#1AFFFFFF</color>
+    <color name="btn_text_leanback_focused">#0E0E0E</color>
+    <color name="btn_text_leanback_unfocused">#E8EAED</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8c5f454..dd048f3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1590,8 +1590,8 @@
          take precedence over lower ones.
          See com.android.server.timedetector.TimeDetectorStrategy for available sources. -->
     <string-array name="config_autoTimeSourcesPriority">
-        <item>telephony</item>
         <item>network</item>
+        <item>telephony</item>
     </string-array>
 
     <!-- Enables the GnssTimeUpdate service. This is the global switch for enabling Gnss time based
@@ -1946,8 +1946,8 @@
     <string name="config_systemAutomotiveProjection" translatable="false"></string>
     <!-- The name of the package that will hold the system cluster service role. -->
     <string name="config_systemAutomotiveCluster" translatable="false"></string>
-    <!-- The name of the package that will hold the system video call role. -->
-    <string name="config_systemVideoCall" translatable="false"></string>
+    <!-- The name of the package that will hold the system shell role. -->
+    <string name="config_systemShell" translatable="false">com.android.shell</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -4712,4 +4712,10 @@
 
     <!-- Whether to select voice/data/sms preference without user confirmation -->
     <bool name="config_voice_data_sms_auto_fallback">false</bool>
+
+    <!-- Whether to enable the one-handed keyguard on the lock screen for wide-screen devices. -->
+    <bool name="config_enableOneHandedKeyguard">false</bool>
+
+    <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot -->
+    <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index cb16af5..7066419 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -236,9 +236,20 @@
          value is calculated in ConversationLayout#updateActionListPadding() -->
     <dimen name="notification_actions_padding_start">36dp</dimen>
 
+    <!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
+         notification actions.
+         this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
+    <dimen name="call_notification_collapsible_indent">64dp</dimen>
+
     <!-- The size of icons for visual actions in the notification_material_action_list -->
     <dimen name="notification_actions_icon_size">48dp</dimen>
 
+    <!-- The size of icons for visual actions in the notification_material_action_list -->
+    <dimen name="notification_actions_icon_drawable_size">20dp</dimen>
+
+    <!-- The corner radius if the emphasized action buttons in a notification -->
+    <dimen name="notification_action_button_radius">8dp</dimen>
+
     <!-- Size of the stroke with for the emphasized notification button style -->
     <dimen name="emphasized_button_stroke_width">1dp</dimen>
 
@@ -383,10 +394,6 @@
     <dimen name="alert_dialog_button_bar_width">64dp</dimen>
     <!-- Dialog button bar height -->
     <dimen name="alert_dialog_button_bar_height">48dip</dimen>
-    <!-- Leanback dialog vertical margin -->
-    <dimen name="leanback_alert_dialog_vertical_margin">27dip</dimen>
-    <!-- Leanback dialog horizontal margin -->
-    <dimen name="leanback_alert_dialog_horizontal_margin">54dip</dimen>
 
     <!-- Default height of an action bar. -->
     <dimen name="action_bar_default_height">48dip</dimen>
@@ -919,4 +926,12 @@
     <dimen name="controls_thumbnail_image_max_height">140dp</dimen>
     <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
     <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
+
+    <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_background_radius">16dp</dimen>
+    <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_inner_radius">8dp</dimen>
+    <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. -->
+    <dimen name="system_app_widget_internal_padding">16dp</dimen>
+
 </resources>
diff --git a/core/res/res/values/dimens_leanback.xml b/core/res/res/values/dimens_leanback.xml
index c824a2a..3ab2196 100644
--- a/core/res/res/values/dimens_leanback.xml
+++ b/core/res/res/values/dimens_leanback.xml
@@ -83,4 +83,10 @@
     <dimen name="leanback_setup_translation_backward_out_content_end_v4">@integer/leanback_setup_translation_content_cliff_v4</dimen>
     <integer name="leanback_setup_translation_backward_out_content_delay">0</integer>
     <integer name="leanback_setup_translation_backward_out_content_duration">@integer/leanback_setup_base_animation_duration</integer>
+
+    <!-- Button dimens -->
+    <dimen name="leanback_button_height">42dp</dimen>
+    <dimen name="leanback_button_radius">55dp</dimen>
+    <dimen name="leanback_button_padding_horizontal">22dp</dimen>
+    <dimen name="leanback_button_padding_vertical">11dp</dimen>
 </resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index a4c7293..ab4e0f3 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -102,6 +102,10 @@
   <item type="id" name="selection_end_handle" />
   <item type="id" name="insertion_handle" />
   <item type="id" name="floating_toolbar_menu_item_image_button" />
+  <item type="id" name="camera" />
+  <item type="id" name="mic" />
+  <item type="id" name="overlay" />
+  <item type="id" name="app_ops" />
 
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_ON_SCREEN}. -->
   <item type="id" name="accessibilityActionShowOnScreen" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 97ec0f4..fdef08b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3062,6 +3062,16 @@
     <!-- @hide @SystemApi -->
     <public name="hotwordDetectionService" />
     <public name="previewLayout" />
+    <public name="clipToOutline" />
+    <public name="edgeEffectType" />
+    <public name="knownCerts" />
+    <public name="windowBackgroundBlurRadius"/>
+    <public name="windowSplashScreenBackground"/>
+    <public name="windowSplashScreenAnimatedIcon"/>
+    <public name="windowSplashScreenAnimationDuration"/>
+    <public name="windowSplashScreenBrandingImage"/>
+    <public name="splashScreenTheme" />
+    <public name="rippleStyle" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
@@ -3102,6 +3112,11 @@
 
   <public-group type="dimen" first-id="0x01050008">
     <!-- dimension definitions go here -->
+
+    <!-- System-provided dimensions for app widgets. -->
+    <public name="system_app_widget_background_radius" />
+    <public name="system_app_widget_inner_radius" />
+    <public name="system_app_widget_internal_padding" />
   </public-group>
 
   <public-group type="bool" first-id="0x01110007">
@@ -3116,9 +3131,9 @@
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveCluster" />
     <!-- @hide @SystemApi @TestApi -->
-    <public name="config_systemVideoCall" />
-    <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveProjection" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemShell" />
   </public-group>
 
   <public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b5f670..af5e406 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5039,6 +5039,18 @@
     <!-- Tempalate for Notification.MessagingStyle to join a conversation name with the name of the sender of a message, to make a notification title [CHAR LIMIT=NONE] -->
     <string name="notification_messaging_title_template"><xliff:g id="conversation_title" example="Tasty Treat Team">%1$s</xliff:g>: <xliff:g id="sender_name" example="Adrian Baker">%2$s</xliff:g></string>
 
+    <!-- Action text to be displayed for the "answer" action of an incoming call [CHAR LIMIT=13] -->
+    <string name="call_notification_answer_action">Answer</string>
+    <!-- Action text to be displayed for the "decline" action of an incoming call [CHAR LIMIT=13] -->
+    <string name="call_notification_decline_action">Decline</string>
+    <!-- Action text to be displayed for the "hang up" action of an ongoing call [CHAR LIMIT=13] -->
+    <string name="call_notification_hang_up_action">Hang Up</string>
+    <!-- Default notification text to be displayed in incoming call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_incoming_text">Incoming call</string>
+    <!-- Default notification text to be displayed in ongoing call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_ongoing_text">Ongoing call</string>
+    <!-- Default notification text to be displayed in screening call notifications [CHAR LIMIT=40] -->
+    <string name="call_notification_screening_text">Screening an incoming call</string>
 
     <!-- Label describing the number of selected items [CHAR LIMIT=48] -->
     <plurals name="selected_count">
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index aaeaadd..7eaf36d 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -16,11 +16,34 @@
 <resources>
     <style name="AlertDialog.Leanback" parent="AlertDialog.Material">
         <item name="buttonPanelSideLayout">@android:layout/alert_dialog_leanback_button_panel_side</item>
+        <item name="layout">@android:layout/alert_dialog_leanback</item>
     </style>
 
     <style name="AlertDialog.Leanback.Light">
     </style>
 
+    <style name="Widget.Leanback.Button" parent="Widget.Material.Button">
+        <item name="android:background">@drawable/btn_leanback</item>
+        <item name="android:textColor">@color/btn_leanback_text_color</item>
+        <item name="android:layout_height">@dimen/leanback_button_height</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textAllCaps">false</item>
+        <item name="android:paddingHorizontal">@dimen/leanback_button_padding_horizontal</item>
+        <item name="android:paddingVertical">@dimen/leanback_button_padding_vertical</item>
+    </style>
+
+    <style name="Widget.Leanback.Button.ButtonBar" parent="Widget.Leanback.Button">
+        <item name="android:layout_marginStart">10dp</item>
+    </style>
+
+    <style name="Widget.Leanback.Button.ButtonBarGravityStart" parent="Widget.Leanback.Button">
+        <item name="android:layout_marginEnd">10dp</item>
+    </style>
+
+    <style name="Widget.Leanback.ButtonBar" parent="Widget.Material.ButtonBar">
+        <item name="android:padding">?android:attr/dialogPreferredPadding</item>
+    </style>
+
     <style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
         <item name="timePickerMode">spinner</item>
     </style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c41b78e..f66d33b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1818,6 +1818,9 @@
   <java-symbol type="string" name="forward_intent_to_owner" />
   <java-symbol type="string" name="forward_intent_to_work" />
   <java-symbol type="dimen" name="cross_profile_apps_thumbnail_size" />
+  <java-symbol type="layout" name="splash_screen_view" />
+  <java-symbol type="id" name="splashscreen_icon_view" />
+  <java-symbol type="id" name="splashscreen_branding_view" />
 
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
@@ -2647,6 +2650,7 @@
   <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" />
   <java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" />
   <java-symbol type="attr" name="colorProgressBackgroundNormal" />
+  <java-symbol type="bool" name="config_allow_pin_storage_for_unattended_reboot" />
 
   <java-symbol type="layout" name="simple_account_item" />
   <java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" />
@@ -3088,8 +3092,26 @@
 
   <!-- TV Remote Service package -->
   <java-symbol type="string" name="config_tvRemoteServicePackage" />
+
+  <!-- Notifications: MessagingStyle -->
   <java-symbol type="string" name="notification_messaging_title_template" />
 
+  <!-- Notifications: CallStyle -->
+  <java-symbol type="layout" name="notification_template_material_call" />
+  <java-symbol type="string" name="call_notification_answer_action" />
+  <java-symbol type="string" name="call_notification_decline_action" />
+  <java-symbol type="string" name="call_notification_hang_up_action" />
+  <java-symbol type="string" name="call_notification_incoming_text" />
+  <java-symbol type="string" name="call_notification_ongoing_text" />
+  <java-symbol type="string" name="call_notification_screening_text" />
+  <java-symbol type="color" name="call_notification_decline_color"/>
+  <java-symbol type="color" name="call_notification_answer_color"/>
+  <java-symbol type="dimen" name="call_notification_collapsible_indent"/>
+  <java-symbol type="drawable" name="ic_call_answer" />
+  <java-symbol type="drawable" name="ic_call_decline" />
+  <java-symbol type="id" name="verification_icon" />
+  <java-symbol type="id" name="verification_text" />
+
   <!-- Notification handler / dashboard package -->
   <java-symbol type="string" name="config_notificationHandlerPackage" />
 
@@ -3415,6 +3437,7 @@
   <java-symbol type="dimen" name="notification_media_image_max_width"/>
   <java-symbol type="dimen" name="notification_media_image_max_height"/>
   <java-symbol type="dimen" name="notification_right_icon_size"/>
+  <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
   <java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
 
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 049ba23..87ae162 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -167,6 +167,9 @@
         <!-- Window attributes -->
         <item name="windowBackground">@drawable/screen_background_selector_dark</item>
         <item name="windowBackgroundFallback">?attr/colorBackground</item>
+        <item name="windowSplashScreenBackground">@color/transparent</item>
+        <item name="windowSplashScreenAnimatedIcon">@null</item>
+        <item name="windowSplashScreenBrandingImage">@null</item>
         <item name="windowClipToOutline">false</item>
         <item name="windowFrame">@null</item>
         <item name="windowNoTitle">false</item>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ce4ee87..e7e049da 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -224,6 +224,9 @@
         <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+        <!-- Ripple style-->
+        <item name="rippleStyle">solid</item>
     </style>
 
     <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 9dca912..efe5826 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -22,6 +22,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+      <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
@@ -32,6 +34,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Settings.Dialog" parent="Theme.Material.Settings.BaseDialog">
@@ -42,6 +46,8 @@
         <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
         <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
         <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+        <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
@@ -52,6 +58,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+      <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.BaseAlert">
@@ -62,6 +70,8 @@
       <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
       <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
       <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+      <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.BaseAlert">
@@ -72,6 +82,8 @@
         <item name="timePickerStyle">@style/Widget.Leanback.TimePicker</item>
         <item name="datePickerStyle">@style/Widget.Leanback.DatePicker</item>
         <item name="numberPickerStyle">@style/Widget.Leanback.NumberPicker</item>
+        <item name="buttonBarButtonStyle">@style/Widget.Leanback.Button.ButtonBarGravityStart</item>
+        <item name="buttonBarStyle">@style/Widget.Leanback.ButtonBar</item>
     </style>
 
     <style name="Theme.Leanback.Dialog.AppError" parent="Theme.Leanback.Dialog">
diff --git a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
similarity index 98%
rename from core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
rename to core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
index 6bcec25..266ff3d 100644
--- a/core/tests/coretests/src/android/app/timedetector/ExternalTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/time/ExternalTimeSuggestionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.app.time;
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
diff --git a/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
new file mode 100644
index 0000000..606e81d
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PermissionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.content.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionInfoTest {
+    private static final String KNOWN_CERT_DIGEST_1 =
+            "6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599";
+    private static final String KNOWN_CERT_DIGEST_2 =
+            "9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6";
+
+    @Test
+    public void createFromParcel_returnsKnownCerts() {
+        // The platform supports a knownSigner permission protection flag that allows one or more
+        // trusted signing certificates to be specified with the permission declaration; if a
+        // requesting app is signed by any of these trusted certificates the permission is granted.
+        // This test verifies the Set of knownCerts is properly parceled / unparceled.
+        PermissionInfo permissionInfo = new PermissionInfo();
+        permissionInfo.protectionLevel =
+                PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+        permissionInfo.knownCerts = new ArraySet<>(2);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_1);
+        permissionInfo.knownCerts.add(KNOWN_CERT_DIGEST_2);
+        Parcel parcel = Parcel.obtain();
+        permissionInfo.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        PermissionInfo unparceledPermissionInfo = PermissionInfo.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(unparceledPermissionInfo.knownCerts);
+        assertEquals(2, unparceledPermissionInfo.knownCerts.size());
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_1));
+        assertTrue(unparceledPermissionInfo.knownCerts.contains(KNOWN_CERT_DIGEST_2));
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index bffd1e4..49b720c 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -29,6 +29,7 @@
 
 import android.content.pm.PackageParser.SigningDetails;
 import android.util.ArraySet;
+import android.util.PackageUtils;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -817,6 +818,124 @@
         assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
     }
 
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullSet_returnsFalse() throws Exception {
+        // The hasAncestorOrSelfWithDigest method is intended to verify whether the SigningDetails
+        // is currently signed, or has previously been signed, by any of the certificate digests
+        // in the provided Set. This test verifies if a null Set is provided then false is returned.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(null));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_unknownDetails_returnsFalse() throws Exception {
+        // If hasAncestorOrSelfWithDigest is invoked against an UNKNOWN
+        // instance of the SigningDetails then false is returned.
+        SigningDetails details = SigningDetails.UNKNOWN;
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerInSet_returnsTrue() throws Exception {
+        // If the single signer of an app is in the provided digest Set then
+        // the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_singleSignerNotInSet_returnsFalse() throws Exception {
+        // If the single signer of an app is not in the provided digest Set then
+        // the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersInSet_returnsTrue() throws Exception {
+        // If an app is signed by multiple signers and all of the signers are in
+        // the digest Set then the method should return true.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersNotInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers then all signers must be in the digest Set; if
+        // only a subset of the signers are in the Set then the method should return false.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_multipleSignersOneInSet_returnsFalse()
+            throws Exception {
+        // If an app is signed by multiple signers and the Set size is smaller than the number of
+        // signers then the method should immediately return false since there's no way for the
+        // requirement of all signers in the Set to be met.
+        SigningDetails details = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerInSet_returnsTrue() throws Exception {
+        // If an app has a rotated signing key and a previous key in the lineage is in the digest
+        // Set then this method should return true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(FIRST_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lineageSignerNotInSet_returnsFalse() throws Exception {
+        // If an app has a rotated signing key, but neither the current key nor any of the signers
+        // in the lineage are in the digest set then the method should return false.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        Set<String> digests = createDigestSet(THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_lastSignerInLineageInSet_returnsTrue()
+            throws Exception {
+        // If an app has multiple signers in the lineage only one of those signers must be in the
+        // Set for this method to return true. This test verifies if the last signer in the lineage
+        // is in the set then the method returns true.
+        SigningDetails details = createSigningDetailsWithLineage(FIRST_SIGNATURE, SECOND_SIGNATURE,
+                THIRD_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE);
+
+        assertTrue(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
+    @Test
+    public void hasAncestorOrSelfWithDigest_nullLineageSingleSIgner_returnsFalse()
+            throws Exception {
+        // Under some instances an app with only a single signer can have a null lineage; this
+        // test verifies that null lineage does not result in a NullPointerException and instead the
+        // method returns false if the single signer is not in the Set.
+        SigningDetails details = createSigningDetails(true, FIRST_SIGNATURE);
+        Set<String> digests = createDigestSet(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertFalse(details.hasAncestorOrSelfWithDigest(digests));
+    }
+
     private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
         int[] capabilities = new int[signers.length];
         for (int i = 0; i < capabilities.length; i++) {
@@ -853,12 +972,21 @@
         // If there are multiple signers then the pastSigningCertificates should be set to null, but
         // if there is only a single signer both the current signer and the past signers should be
         // set to that one signer.
-        if (signers.length > 1) {
+        if (signers.length > 1 || useNullPastSigners) {
             return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
         }
         return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
     }
 
+    private Set<String> createDigestSet(String... signers) {
+        Set<String> digests = new ArraySet<>();
+        for (String signer : signers) {
+            String digest = PackageUtils.computeSha256Digest(new Signature(signer).toByteArray());
+            digests.add(digest);
+        }
+        return digests;
+    }
+
     private void assertSigningDetailsContainsLineage(SigningDetails details,
             String... pastSigners) {
         // This method should only be invoked for results that contain a single signer.
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 564103e..11239db 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -17,6 +17,8 @@
 package android.os;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import static org.testng.Assert.assertThrows;
 
@@ -31,7 +33,6 @@
 @Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationEffectTest {
-
     private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
     private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
 
@@ -172,6 +173,37 @@
     }
 
     @Test
+    public void testHasVibratorMono_returnsTrueForAnyVibrator() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        assertTrue(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+    }
+
+    @Test
+    public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertFalse(effect.hasVibrator(2));
+    }
+
+    @Test
+    public void testHasVibratorSequential_returnsNestedVibrators() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(CombinedVibrationEffect.startSynced()
+                        .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                        .combine())
+                .combine();
+        assertFalse(effect.hasVibrator(0));
+        assertTrue(effect.hasVibrator(1));
+        assertTrue(effect.hasVibrator(2));
+    }
+
+    @Test
     public void testSerializationMono() {
         CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 1a28b73..9a9b474 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -2,8 +2,11 @@
 per-file BrightnessLimit.java = michaelwr@google.com, santoscordon@google.com
 
 # Haptics
+per-file CombinedVibrationEffectTest.java = michaelwr@google.com
 per-file ExternalVibrationTest.java = michaelwr@google.com
 per-file VibrationEffectTest.java = michaelwr@google.com
+per-file VibratorInfoTest.java = michaelwr@google.com
+per-file VibratorTest.java = michaelwr@google.com
 
 # Power
 per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS
new file mode 100644
index 0000000..581da71
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/provider/OWNERS
diff --git a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
index a43b238..a121941 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationListenerFilterTest.java
@@ -16,14 +16,14 @@
 
 package android.service.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
 import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.NotificationChannel;
+import android.content.pm.VersionedPackage;
 import android.os.Parcel;
 import android.util.ArraySet;
 
@@ -45,16 +45,19 @@
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
         assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS
                 | FLAG_FILTER_TYPE_ALERTING
-                | FLAG_FILTER_TYPE_SILENT);
+                | FLAG_FILTER_TYPE_SILENT
+                | FLAG_FILTER_TYPE_ONGOING);
 
         assertThat(nlf.getDisallowedPackages()).isEmpty();
-        assertThat(nlf.isPackageAllowed("pkg1")).isTrue();
+        assertThat(nlf.isPackageAllowed(new VersionedPackage("any", 0))).isTrue();
     }
 
 
     @Test
     public void testConstructor() {
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
@@ -62,20 +65,21 @@
         assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
         assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
 
-        assertThat(nlf.getDisallowedPackages()).contains("pkg1");
-        assertThat(nlf.getDisallowedPackages()).contains("pkg2");
-        assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
-        assertThat(nlf.isPackageAllowed("pkg2")).isFalse();
+        assertThat(nlf.getDisallowedPackages()).contains(a1);
+        assertThat(nlf.getDisallowedPackages()).contains(a2);
+        assertThat(nlf.isPackageAllowed(a1)).isFalse();
+        assertThat(nlf.isPackageAllowed(a2)).isFalse();
     }
 
     @Test
     public void testSetDisallowedPackages() {
         NotificationListenerFilter nlf = new NotificationListenerFilter();
 
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"});
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(
+                new VersionedPackage[] {new VersionedPackage("pkg1", 0)});
         nlf.setDisallowedPackages(pkgs);
 
-        assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
+        assertThat(nlf.isPackageAllowed(new VersionedPackage("pkg1", 0))).isFalse();
     }
 
     @Test
@@ -94,7 +98,9 @@
     @Test
     public void testDescribeContents() {
         final int expected = 0;
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
         assertThat(nlf.describeContents()).isEqualTo(expected);
@@ -102,7 +108,9 @@
 
     @Test
     public void testParceling() {
-        ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        VersionedPackage a2= new VersionedPackage("pkg2", 2142534);
+        ArraySet<VersionedPackage> pkgs = new ArraySet<>(new VersionedPackage[] {a1, a2});
         NotificationListenerFilter nlf =
                 new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
 
@@ -116,9 +124,9 @@
         assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
         assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
 
-        assertThat(nlf1.getDisallowedPackages()).contains("pkg1");
-        assertThat(nlf1.getDisallowedPackages()).contains("pkg2");
-        assertThat(nlf1.isPackageAllowed("pkg1")).isFalse();
-        assertThat(nlf1.isPackageAllowed("pkg2")).isFalse();
+        assertThat(nlf1.getDisallowedPackages()).contains(a1);
+        assertThat(nlf1.getDisallowedPackages()).contains(a2);
+        assertThat(nlf1.isPackageAllowed(a1)).isFalse();
+        assertThat(nlf1.isPackageAllowed(a2)).isFalse();
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index d2107ea..08205b4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -218,7 +218,7 @@
         sippers.add(sipperBg);
         sippers.add(sipperFg);
 
-        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper);
+        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper, 0);
 
         assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0);
         assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0);
@@ -253,7 +253,7 @@
         doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
                 anyLong(), anyInt());
 
-        final long time = spc.getProcessForegroundTimeMs(mUid);
+        final long time = spc.getProcessForegroundTimeMs(mUid, 1000);
 
         assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
     }
@@ -296,7 +296,7 @@
         doReturn(uidCode).when(sipper).getUid();
         if (!isUidNull) {
             final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
-            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid));
+            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid), anyLong());
             doReturn(uidCode).when(uid).getUid();
             sipper.uidObj = uid;
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 97c07ea..6652c64 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -24,6 +24,7 @@
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.Uid.Sensor;
 import android.os.WorkSource;
+import android.util.SparseLongArray;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -583,6 +584,95 @@
         checkMeasuredEnergy("H", uid1, blame1, uid2, blame2, globalDoze, bi);
     }
 
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked_neverCalled() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setOnBatteryInternal(true);
+
+        final int uid1 = 11500;
+        final int uid2 = 11501;
+
+        // Initially, all custom buckets report energy of 0.
+        checkCustomMeasuredEnergy("0", 0, 0, uid1, 0, 0, uid2, 0, 0, bi);
+    }
+
+    @SmallTest
+    public void testUpdateCustomMeasuredEnergyDataLocked() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        final int bucketA = 0; // Custom bucket 0
+        final int bucketB = 1; // Custom bucket 1
+
+        long totalBlameA = 0; // Total energy consumption for bucketA (may exceed sum of uids)
+        long totalBlameB = 0; // Total energy consumption for bucketB (may exceed sum of uids)
+
+        final int uid1 = 10500;
+        long blame1A = 0; // Blame for uid1 in bucketA
+        long blame1B = 0; // Blame for uid1 in bucketB
+
+        final int uid2 = 10501;
+        long blame2A = 0; // Blame for uid2 in bucketA
+        long blame2B = 0; // Blame for uid2 in bucketB
+
+        final SparseLongArray newEnergiesA = new SparseLongArray(2);
+        final SparseLongArray newEnergiesB = new SparseLongArray(2);
+
+
+        // ----- Case A: battery off (so blame does not increase)
+        bi.setOnBatteryInternal(false);
+
+        newEnergiesA.put(uid1, 20_000);
+        // Implicit newEnergiesA.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 500_000, newEnergiesA);
+
+        newEnergiesB.put(uid1, 60_000);
+        // Implicit newEnergiesB.put(uid2, 0);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 700_000, newEnergiesB);
+
+        checkCustomMeasuredEnergy(
+                "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case B: battery on
+        bi.setOnBatteryInternal(true);
+
+        newEnergiesA.put(uid1, 7_000); blame1A += 7_000;
+        // Implicit newEnergiesA.put(uid2, 0); blame2A += 0;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 310_000, newEnergiesA);
+        totalBlameA += 310_000;
+
+        newEnergiesB.put(uid1, 63_000); blame1B += 63_000;
+        newEnergiesB.put(uid2, 15_000); blame2B += 15_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 790_000, newEnergiesB);
+        totalBlameB += 790_000;
+
+        checkCustomMeasuredEnergy(
+                "B", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case C: battery still on
+        newEnergiesA.delete(uid1); blame1A += 0;
+        newEnergiesA.put(uid2, 16_000); blame2A += 16_000;
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 560_000, newEnergiesA);
+        totalBlameA += 560_000;
+
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 10_000, null);
+        totalBlameB += 10_000;
+
+        checkCustomMeasuredEnergy(
+                "C", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+
+
+        // ----- Case D: battery still on
+        bi.updateCustomMeasuredEnergyDataLocked(bucketA, 0, newEnergiesA);
+        bi.updateCustomMeasuredEnergyDataLocked(bucketB, 15_000, new SparseLongArray(1));
+        totalBlameB += 15_000;
+        checkCustomMeasuredEnergy(
+                "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
+    }
+
     private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) {
         // Note that noteUidProcessStateLocked uses ActivityManager process states.
         if (fgOn) {
@@ -610,4 +700,33 @@
         assertEquals("Wrong doze for Case " + caseName, globalDoze,
                 bi.getScreenDozeEnergy());
     }
+
+    private void checkCustomMeasuredEnergy(String caseName,
+            long totalBlameA, long totalBlameB,
+            int uid1, long blame1A, long blame1B,
+            int uid2, long blame2A, long blame2B,
+            MockBatteryStatsImpl bi) {
+
+        final long[] actualTotal = bi.getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid1 = bi.getUidStatsLocked(uid1).getCustomMeasuredEnergiesMicroJoules();
+        final long[] actualUid2 = bi.getUidStatsLocked(uid2).getCustomMeasuredEnergiesMicroJoules();
+
+        assertNotNull(actualTotal);
+        assertNotNull(actualUid1);
+        assertNotNull(actualUid2);
+
+        assertEquals("Wrong total blame in bucket 0 for Case " + caseName, totalBlameA,
+                actualTotal[0]);
+
+        assertEquals("Wrong total blame in bucket 1 for Case " + caseName, totalBlameB,
+                actualTotal[1]);
+
+        assertEquals("Wrong uid1 blame in bucket 0 for Case " + caseName, blame1A, actualUid1[0]);
+
+        assertEquals("Wrong uid1 blame in bucket 1 for Case " + caseName, blame1B, actualUid1[1]);
+
+        assertEquals("Wrong uid2 blame in bucket 0 for Case " + caseName, blame2A, actualUid2[0]);
+
+        assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 143e07a..2e6e0de 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -45,6 +45,7 @@
         BluetoothPowerCalculatorTest.class,
         BstatsCpuTimesValidationTest.class,
         CameraPowerCalculatorTest.class,
+        CpuPowerCalculatorTest.class,
         FlashlightPowerCalculatorTest.class,
         GnssPowerCalculatorTest.class,
         IdlePowerCalculatorTest.class,
@@ -65,7 +66,9 @@
         ScreenPowerCalculatorTest.class,
         SensorPowerCalculatorTest.class,
         SystemServicePowerCalculatorTest.class,
+        UserPowerCalculatorTest.class,
         VideoPowerCalculatorTest.class,
+        WakelockPowerCalculatorTest.class,
 
         com.android.internal.power.MeasuredEnergyStatsTest.class
     })
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 2c71287..5edd58f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -28,6 +28,7 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
@@ -48,6 +49,7 @@
     };
 
     private BatteryUsageStats mBatteryUsageStats;
+    private boolean mScreenOn;
 
     public BatteryUsageStatsRule() {
         Context context = InstrumentationRegistry.getContext();
@@ -76,6 +78,31 @@
         return this;
     }
 
+    public BatteryUsageStatsRule setNumCpuClusters(int number) {
+        when(mPowerProfile.getNumCpuClusters()).thenReturn(number);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) {
+        when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) {
+        when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value);
+        return this;
+    }
+
+    public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) {
+        when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value);
+        return this;
+    }
+
+    public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) {
+        mScreenOn = screenOn;
+        return this;
+    }
+
     public void setNetworkStats(NetworkStats networkStats) {
         mBatteryStats.setNetworkStats(networkStats);
     }
@@ -94,6 +121,7 @@
     private void noteOnBattery() {
         mBatteryStats.setOnBatteryInternal(true);
         mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
+        mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0);
     }
 
     public PowerProfile getPowerProfile() {
@@ -113,15 +141,21 @@
         mMockClocks.uptime = uptimeUs;
     }
 
-    void apply(PowerCalculator calculator) {
+    void apply(PowerCalculator... calculators) {
+        apply(BatteryUsageStatsQuery.DEFAULT, calculators);
+    }
+
+    void apply(BatteryUsageStatsQuery query, PowerCalculator... calculators) {
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
         }
 
-        calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
-                BatteryUsageStatsQuery.DEFAULT, null);
+        for (PowerCalculator calculator : calculators) {
+            calculator.calculate(builder, mBatteryStats, mMockClocks.realtime, mMockClocks.uptime,
+                    query);
+        }
 
         mBatteryUsageStats = builder.build();
     }
@@ -144,4 +178,13 @@
         }
         return null;
     }
+
+    public UserBatteryConsumer getUserBatteryConsumer(int userId) {
+        for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) {
+            if (ubc.getUserId() == userId) {
+                return ubc;
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
new file mode 100644
index 0000000..9cf0d37
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryConsumer;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CpuPowerCalculatorTest {
+    private static final double PRECISION = 0.00001;
+
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setNumCpuClusters(2)
+            .setNumSpeedStepsInCpuCluster(0, 2)
+            .setNumSpeedStepsInCpuCluster(1, 2)
+            .setAveragePowerForCpuCluster(0, 360)
+            .setAveragePowerForCpuCluster(1, 480)
+            .setAveragePowerForCpuCore(0, 0, 300)
+            .setAveragePowerForCpuCore(0, 1, 400)
+            .setAveragePowerForCpuCore(1, 0, 500)
+            .setAveragePowerForCpuCore(1, 1, 600);
+
+    private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{
+            mock(KernelCpuSpeedReader.class),
+            mock(KernelCpuSpeedReader.class),
+    };
+
+    @Mock
+    private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader mMockKernelCpuUidClusterTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mMockKernelCpuUidUserSysTimeReader;
+    @Mock
+    private KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader mMockKerneCpuUidActiveTimeReader;
+    @Mock
+    private SystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mStatsRule.getBatteryStats()
+                .setUserInfoProvider(mMockUserInfoProvider)
+                .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
+                .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
+                .setKernelCpuUidClusterTimeReader(mMockKernelCpuUidClusterTimeReader)
+                .setKernelCpuUidUserSysTimeReader(mMockKernelCpuUidUserSysTimeReader)
+                .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
+                .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader);
+    }
+
+    @Test
+    public void testTimerBasedModel() {
+        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+        when(mMockKernelCpuSpeedReaders[0].readDelta()).thenReturn(new long[]{1000, 2000});
+        when(mMockKernelCpuSpeedReaders[1].readDelta()).thenReturn(new long[]{3000, 4000});
+
+        when(mMockCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(false);
+
+        // User/System CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+            // User/system time in microseconds
+            callback.onUidCpuTime(APP_UID1, new long[]{1111000, 2222000});
+            callback.onUidCpuTime(APP_UID2, new long[]{3333000, 4444000});
+            return null;
+        }).when(mMockKernelCpuUidUserSysTimeReader).readDelta(any());
+
+        // Active CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<Long> callback = invocation.getArgument(0);
+            callback.onUidCpuTime(APP_UID1, 1111L);
+            callback.onUidCpuTime(APP_UID2, 3333L);
+            return null;
+        }).when(mMockKerneCpuUidActiveTimeReader).readDelta(any());
+
+        // Per-cluster CPU time
+        doAnswer(invocation -> {
+            final KernelCpuUidTimeReader.Callback<long[]> callback = invocation.getArgument(0);
+            callback.onUidCpuTime(APP_UID1, new long[]{1111, 2222});
+            callback.onUidCpuTime(APP_UID2, new long[]{3333, 4444});
+            return null;
+        }).when(mMockKernelCpuUidClusterTimeReader).readDelta(any());
+
+        mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true);
+
+        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234);
+        mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345);
+
+        CpuPowerCalculator calculator =
+                new CpuPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        UidBatteryConsumer uidConsumer1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uidConsumer1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+                .isEqualTo(3333);
+        assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(1.092233);
+        assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
+
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU))
+                .isEqualTo(7777);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(2.672322);
+        assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index fdfc7ac..d50bb05 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -232,7 +232,7 @@
         assertThat(entry3.handlerClassName).isEqualTo(
                 "com.android.internal.os.LooperStatsTest$TestHandlerSecond");
         assertThat(entry3.messageName).startsWith(
-                "com.android.internal.os.-$$Lambda$LooperStatsTest$");
+                "com.android.internal.os.LooperStatsTest-$$ExternalSyntheticLambda");
         assertThat(entry3.messageCount).isEqualTo(1);
         assertThat(entry3.recordedMessageCount).isEqualTo(1);
         assertThat(entry3.exceptionCount).isEqualTo(0);
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 e43caa3..717fac0 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -18,8 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.ActivityManager;
 import android.os.BatteryConsumer;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
 import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -33,20 +37,37 @@
 @SmallTest
 public class ScreenPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
+    private static final long MINUTE_IN_MS = 60 * 1000;
+    private static final long MINUTE_IN_US = 60 * 1000 * 1000;
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_SCREEN_ON, 360.0)
-            .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 3600.0);
+            .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0)
+            .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0);
 
     @Test
-    public void testTimerBasedModel() {
-        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+    public void testEnergyBasedModel() {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        stats.noteScreenStateLocked(Display.STATE_ON, 1000, 1000, 1000);
-        stats.noteScreenBrightnessLocked(100, 1000, 1000);
-        stats.noteScreenBrightnessLocked(200, 2000, 2000);
-        stats.noteScreenStateLocked(Display.STATE_OFF, 3000, 3000, 3000);
+        batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+        batteryStats.updateDisplayEnergyLocked(0, Display.STATE_ON, 2 * MINUTE_IN_MS);
+
+        setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+        setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+        setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+        batteryStats.updateDisplayEnergyLocked(300_000_000, Display.STATE_ON,
+                60 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+        batteryStats.updateDisplayEnergyLocked(100_000_000, Display.STATE_DOZE,
+                120 * MINUTE_IN_MS);
+
+        mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
 
         ScreenPowerCalculator calculator =
                 new ScreenPowerCalculator(mStatsRule.getPowerProfile());
@@ -56,8 +77,79 @@
         SystemBatteryConsumer consumer =
                 mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
         assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
-                .isEqualTo(2000);
+                .isEqualTo(80 * MINUTE_IN_MS);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
-                .isWithin(PRECISION).of(1.2);
+                .isWithin(PRECISION).of(30.03003);
+
+        UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(18 * MINUTE_IN_MS);
+        assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(8.44594);
+
+        UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(90 * MINUTE_IN_MS);
+        assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(21.58408);
+    }
+
+    @Test
+    public void testPowerProfileBasedModel() {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
+
+        setFgState(APP_UID1, true, 2 * MINUTE_IN_MS, 2 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+        batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+        setFgState(APP_UID1, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+        setFgState(APP_UID2, true, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+
+        batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+                80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+        mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+
+        ScreenPowerCalculator calculator =
+                new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder().powerProfileModeledOnly().build(),
+                calculator);
+
+        SystemBatteryConsumer consumer =
+                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+                .isEqualTo(80 * MINUTE_IN_MS);
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+                .isWithin(PRECISION).of(88.4);
+
+        UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+        assertThat(uid1.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(18 * MINUTE_IN_MS);
+        assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(14.73333);
+
+        UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uid2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
+                .isEqualTo(90 * MINUTE_IN_MS);
+        assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(73.66666);
+    }
+
+    private void setFgState(int uid, boolean fgOn, long realtimeMs, long uptimeMs) {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+        if (fgOn) {
+            batteryStats.noteActivityResumedLocked(uid, realtimeMs, uptimeMs);
+            batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_TOP,
+                    realtimeMs, uptimeMs);
+        } else {
+            batteryStats.noteActivityPausedLocked(uid, realtimeMs, uptimeMs);
+            batteryStats.noteUidProcessStateLocked(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+                    realtimeMs, uptimeMs);
+        }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 24741fe..dfbf28b 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -18,9 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Binder;
 import android.os.Process;
+import android.os.UidBatteryConsumer;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -39,34 +46,48 @@
 @RunWith(AndroidJUnit4.class)
 public class SystemServicePowerCalculatorTest {
 
-    private static final double PRECISION = 0.0000001;
+    private static final double PRECISION = 0.000001;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720)
+            .setNumCpuClusters(2)
+            .setNumSpeedStepsInCpuCluster(0, 2)
+            .setNumSpeedStepsInCpuCluster(1, 2)
+            .setAveragePowerForCpuCluster(0, 360)
+            .setAveragePowerForCpuCluster(1, 480)
+            .setAveragePowerForCpuCore(0, 0, 300)
+            .setAveragePowerForCpuCore(0, 1, 400)
+            .setAveragePowerForCpuCore(1, 0, 500)
+            .setAveragePowerForCpuCore(1, 1, 600);
 
+    private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider;
     private MockBatteryStatsImpl mMockBatteryStats;
     private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
     private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
 
     @Before
     public void setUp() throws IOException {
+        mMockUserInfoProvider = mock(BatteryStatsImpl.UserInfoProvider.class);
         mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
         mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
         mMockBatteryStats = mStatsRule.getBatteryStats()
                 .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
                 .setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
-                .setUserInfoProvider(new MockUserInfoProvider());
+                .setUserInfoProvider(mMockUserInfoProvider);
     }
 
     @Test
-    public void testCalculateApp() {
-        // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
+    public void testPowerProfileBasedModel() {
+        when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
+
+        // Test Power Profile has two CPU clusters with 2 speeds each, thus 4 freq times total
         mMockSystemServerCpuThreadReader.setCpuTimes(
-                new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
-                new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
+                new long[] {30000, 40000, 50000, 60000},
+                new long[] {20000, 30000, 40000, 50000});
 
         mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
-                new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000}
+                new long[] {10000, 20000, 30000, 40000}
         );
 
         mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -101,14 +122,17 @@
         SystemServicePowerCalculator calculator = new SystemServicePowerCalculator(
                 mStatsRule.getPowerProfile());
 
-        mStatsRule.apply(calculator);
+        mStatsRule.apply(new FakeCpuPowerCalculator(), calculator);
 
         assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid1)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
-                .isWithin(PRECISION).of(0.00016269);
+                .isWithin(PRECISION).of(1.888888);
         assertThat(mStatsRule.getUidBatteryConsumer(workSourceUid2)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
-                .isWithin(PRECISION).of(0.00146426);
+                .isWithin(PRECISION).of(17.0);
+        assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
+                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
+                .isWithin(PRECISION).of(-18.888888);
     }
 
     private static class MockKernelCpuUidFreqTimeReader extends
@@ -137,7 +161,7 @@
     }
 
     private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader {
-        private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
+        private final SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
 
         MockSystemServerCpuThreadReader() {
             super(null);
@@ -154,16 +178,15 @@
         }
     }
 
-    private static class MockUserInfoProvider extends BatteryStatsImpl.UserInfoProvider {
-        @Nullable
+    private static class FakeCpuPowerCalculator extends PowerCalculator {
         @Override
-        protected int[] getUserIds() {
-            return new int[0];
-        }
-
-        @Override
-        public boolean exists(int userId) {
-            return true;
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            if (u.getUid() == Process.SYSTEM_UID) {
+                // SystemServer must be attributed at least as much power as the total
+                // of all system services requested by apps.
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 1000000);
+            }
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
new file mode 100644
index 0000000..6fa1d3b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/UserPowerCalculatorTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.UserBatteryConsumer;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserPowerCalculatorTest {
+    public static final int USER1 = 0;
+    public static final int USER2 = 1625;
+
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 272;
+    private static final int APP_UID3 = Process.FIRST_APPLICATION_UID + 314;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
+
+    @Test
+    public void testAllUsers() {
+        prepareUidBatteryConsumers();
+
+        UserPowerCalculator calculator = new UserPowerCalculator();
+
+        mStatsRule.apply(BatteryUsageStatsQuery.DEFAULT, calculator, new FakeAudioPowerCalculator(),
+                new FakeVideoPowerCalculator());
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER2)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(5555);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(9999);
+    }
+
+    @Test
+    public void testSpecificUser() {
+        prepareUidBatteryConsumers();
+
+        UserPowerCalculator calculator = new UserPowerCalculator();
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder().addUser(UserHandle.of(USER1)).build(),
+                calculator, new FakeAudioPowerCalculator(), new FakeVideoPowerCalculator());
+
+        assertThat(mStatsRule.getUserBatteryConsumer(USER1)).isNull();
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(3000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID1))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(7000);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID2))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO)).isEqualTo(7070);
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER1, APP_UID3))
+                .getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO)).isEqualTo(11110);
+
+        UserBatteryConsumer user2 = mStatsRule.getUserBatteryConsumer(USER2);
+        assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO))
+                .isEqualTo(15308);
+        assertThat(user2.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO))
+                .isEqualTo(24196);
+
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID1))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID2))).isNull();
+        assertThat(mStatsRule.getUidBatteryConsumer(UserHandle.getUid(USER2, APP_UID3))).isNull();
+    }
+
+    private void prepareUidBatteryConsumers() {
+        prepareUidBatteryConsumer(USER1, APP_UID1, 1000, 2000, 3000, 4000);
+        prepareUidBatteryConsumer(USER2, APP_UID2, 2222, 3333, 4444, 5555);
+        prepareUidBatteryConsumer(USER1, APP_UID3, 3030, 4040, 5050, 6060);
+        prepareUidBatteryConsumer(USER2, APP_UID3, 4321, 5432, 6543, 7654);
+    }
+
+    private void prepareUidBatteryConsumer(int userId, int uid, long audioDuration1Ms,
+            long audioDuration2Ms, long videoDuration1Ms, long videoDuration2Ms) {
+        BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(UserHandle.getUid(userId, uid));
+
+        // Use "audio" and "video" to fake some power consumption. Could be any other type of usage.
+        uidStats.noteAudioTurnedOnLocked(0);
+        uidStats.noteAudioTurnedOffLocked(audioDuration1Ms);
+        uidStats.noteAudioTurnedOnLocked(1000000);
+        uidStats.noteAudioTurnedOffLocked(1000000 + audioDuration2Ms);
+
+        uidStats.noteVideoTurnedOnLocked(0);
+        uidStats.noteVideoTurnedOffLocked(videoDuration1Ms);
+        uidStats.noteVideoTurnedOnLocked(2000000);
+        uidStats.noteVideoTurnedOffLocked(2000000 + videoDuration2Ms);
+    }
+
+    private static class FakeAudioPowerCalculator extends PowerCalculator {
+        @Override
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            long durationMs = u.getAudioTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_AUDIO, durationMs / 1000);
+        }
+    }
+
+    private static class FakeVideoPowerCalculator extends PowerCalculator {
+        @Override
+        protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+                long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+            long durationMs = u.getVideoTurnedOnTimer().getTotalTimeLocked(rawRealtimeUs, 0);
+            app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_VIDEO, durationMs / 1000);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
new file mode 100644
index 0000000..4f71b43
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.WorkSource;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WakelockPowerCalculatorTest {
+    private static final double PRECISION = 0.00001;
+
+    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_PID = 3145;
+
+    @Rule
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CPU_IDLE, 360.0);
+
+    @Test
+    public void testTimerBasedModel() {
+        mStatsRule.getUidStats(Process.ROOT_UID);
+
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        batteryStats.noteStartWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+                BatteryStats.WAKE_TYPE_PARTIAL, true, 1000, 1000);
+        batteryStats.noteStopWakeFromSourceLocked(new WorkSource(APP_UID), APP_PID, "awake", "",
+                BatteryStats.WAKE_TYPE_PARTIAL, 2000, 2000);
+
+        mStatsRule.setTime(10_000_000, 6_000_000);
+
+        WakelockPowerCalculator calculator =
+                new WakelockPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(calculator);
+
+        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+                .isEqualTo(1000);
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.1);
+
+        UidBatteryConsumer osConsumer = mStatsRule.getUidBatteryConsumer(Process.ROOT_UID);
+        assertThat(osConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WAKELOCK))
+                .isEqualTo(5000);
+        assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.5);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index b9908f4..5fd5a78 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -387,6 +387,59 @@
     }
 
     @Test
+    public void testIsValidCustomBucket() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+        assertFalse(stats.isValidCustomBucket(-1));
+        assertTrue(stats.isValidCustomBucket(0));
+        assertTrue(stats.isValidCustomBucket(1));
+        assertTrue(stats.isValidCustomBucket(2));
+        assertFalse(stats.isValidCustomBucket(3));
+        assertFalse(stats.isValidCustomBucket(4));
+
+        final MeasuredEnergyStats boringStats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+        assertFalse(boringStats.isValidCustomBucket(-1));
+        assertFalse(boringStats.isValidCustomBucket(0));
+        assertFalse(boringStats.isValidCustomBucket(1));
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3);
+
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
+        stats.updateCustomBucket(2, 13, true);
+        stats.updateCustomBucket(1, 70, true);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(3, output.length);
+
+        assertEquals(50, output[0]);
+        assertEquals(60 + 70, output[1]);
+        assertEquals(13, output[2]);
+    }
+
+    @Test
+    public void testGetAccumulatedCustomBucketEnergies_empty() {
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0);
+
+        final long[] output = stats.getAccumulatedCustomBucketEnergies();
+        assertEquals(0, output.length);
+    }
+
+    @Test
+    public void testGetNumberCustomEnergyBuckets() {
+        assertEquals(0, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 0)
+                .getNumberCustomEnergyBuckets());
+        assertEquals(3, new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3)
+                .getNumberCustomEnergyBuckets());
+    }
+
+    @Test
     public void testReset() {
         final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
         final int numCustomBuckets = 2;
diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS
new file mode 100644
index 0000000..b40fe24
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS
@@ -0,0 +1,3 @@
+# LockSettings related
+per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/tests/overlaytests/device/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml
index e918268..a30d66f 100644
--- a/core/tests/overlaytests/device/res/values/config.xml
+++ b/core/tests/overlaytests/device/res/values/config.xml
@@ -2,6 +2,7 @@
 <resources>
     <string name="str">none</string>
     <string name="str2">none</string>
+    <integer name="overlaid">0</integer>
     <integer name="matrix_100000">100</integer>
     <integer name="matrix_100001">100</integer>
     <integer name="matrix_100010">100</integer>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
new file mode 100644
index 0000000..3465989
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.overlaytest;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public class FabricatedOverlaysTest {
+    private static final String TAG = "FabricatedOverlaysTest";
+    private final String TEST_RESOURCE = "integer/overlaid";
+    private final String TEST_OVERLAY_NAME = "Test";
+
+    private Context mContext;
+    private Resources mResources;
+    private OverlayManager mOverlayManager;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResources = mContext.getResources();
+        mOverlayManager = mContext.getSystemService(OverlayManager.class);
+        mUserId = UserHandle.myUserId();
+        mUserHandle = UserHandle.of(mUserId);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+        mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+                info -> {
+                    if (info.isFabricated()) {
+                        cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+                    }
+                });
+        mOverlayManager.commit(cleanUp.build());
+    }
+
+    @Test
+    public void testFabricatedOverlay() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+
+        OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertFalse(info.isEnabled());
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertTrue(info.isEnabled());
+
+        waitForResourceValue(1);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .unregisterFabricatedOverlay(overlay.getIdentifier())
+                .build());
+
+        waitForResourceValue(0);
+    }
+
+    @Test
+    public void testRegisterEnableAtomic() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+    }
+
+    @Test
+    public void testRegisterTwice() throws Exception {
+        FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        waitForResourceValue(1);
+        overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 2)
+                .build();
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        waitForResourceValue(2);
+    }
+
+    @Test
+    public void testInvalidOwningPackageName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .setEnabled(overlay.getIdentifier(), true, mUserId)
+                    .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testInvalidOverlayName() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), "invalid@name", mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testOverlayIdentifierLongest() throws Exception {
+        final int maxLength = 255 - 11; // 11 reserved characters
+        final String longestName = String.join("",
+                Collections.nCopies(maxLength - mContext.getPackageName().length(), "a"));
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName, mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                    .registerFabricatedOverlay(overlay)
+                    .build());
+            assertNotNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+        {
+            FabricatedOverlay overlay = new FabricatedOverlay.Builder(mContext.getPackageName(),
+                    longestName + "a", mContext.getPackageName())
+                    .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                    .build();
+
+            assertThrows(SecurityException.class, () ->
+                    mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                            .registerFabricatedOverlay(overlay)
+                            .build()));
+
+            assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+        }
+    }
+
+    @Test
+    public void testInvalidResourceValues() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                "android", TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    @Test
+    public void testTransactionFailRollback() throws Exception {
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
+                .build();
+
+        waitForResourceValue(0);
+        assertThrows(SecurityException.class, () ->
+                mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                        .registerFabricatedOverlay(overlay)
+                        .setEnabled(overlay.getIdentifier(), true, mUserId)
+                        .setEnabled(new OverlayIdentifier("not-valid"), true, mUserId)
+                        .build()));
+
+        assertNull(mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle));
+    }
+
+    void waitForResourceValue(final int expectedValue) throws TimeoutException {
+        final long timeOutDuration = 10000;
+        final long endTime = System.currentTimeMillis() + timeOutDuration;
+        final String resourceName = TEST_RESOURCE;
+        final int resourceId = mResources.getIdentifier(resourceName, "",
+                mContext.getPackageName());
+        int resourceValue = 0;
+        while (System.currentTimeMillis() < endTime) {
+            resourceValue = mResources.getInteger(resourceId);
+            if (resourceValue == expectedValue) {
+                return;
+            }
+        }
+        final String paths = TextUtils.join(",", mResources.getAssets().getApkPaths());
+        Log.w(TAG, "current paths: [" + paths + "]", new Throwable());
+        throw new TimeoutException("Timed out waiting for '" + resourceName + "' value to equal '"
+                + expectedValue + "': current value is '" + resourceValue + "'");
+    }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
index 76c01a7..3c0c131 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayManager;
 import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
@@ -32,14 +33,14 @@
 class LocalOverlayManager {
     private static final long TIMEOUT = 30;
 
-    public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable,
-            @NonNull final String[] overlaysToDisable) throws Exception {
+    public static void toggleOverlaysAndWait(@NonNull final OverlayIdentifier[] overlaysToEnable,
+            @NonNull final OverlayIdentifier[] overlaysToDisable) throws Exception {
         final int userId = UserHandle.myUserId();
         OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
-        for (String pkg : overlaysToEnable) {
+        for (OverlayIdentifier pkg : overlaysToEnable) {
             builder.setEnabled(pkg, true, userId);
         }
-        for (String pkg : overlaysToDisable) {
+        for (OverlayIdentifier pkg : overlaysToDisable) {
             builder.setEnabled(pkg, false, userId);
         }
         OverlayManagerTransaction transaction = builder.build();
@@ -48,7 +49,7 @@
         FutureTask<Boolean> task = new FutureTask<>(() -> {
             while (true) {
                 final String[] paths = ctx.getResources().getAssets().getApkPaths();
-                if (arrayTailContains(paths, overlaysToEnable)
+                if (arrayTailContainsOverlays(paths, overlaysToEnable)
                         && arrayDoesNotContain(paths, overlaysToDisable)) {
                     return true;
                 }
@@ -64,15 +65,15 @@
         task.get(TIMEOUT, SECONDS);
     }
 
-    private static boolean arrayTailContains(@NonNull final String[] array,
-            @NonNull final String[] substrings) {
-        if (array.length < substrings.length) {
+    private static boolean arrayTailContainsOverlays(@NonNull final String[] array,
+            @NonNull final OverlayIdentifier[] overlays) {
+        if (array.length < overlays.length) {
             return false;
         }
-        for (int i = 0; i < substrings.length; i++) {
-            String a = array[array.length - substrings.length + i];
-            String s = substrings[i];
-            if (!a.contains(s)) {
+        for (int i = 0; i < overlays.length; i++) {
+            String a = array[array.length - overlays.length + i];
+            OverlayIdentifier s = overlays[i];
+            if (!a.contains(s.getPackageName())) {
                 return false;
             }
         }
@@ -80,10 +81,10 @@
     }
 
     private static boolean arrayDoesNotContain(@NonNull final String[] array,
-            @NonNull final String[] substrings) {
-        for (String s : substrings) {
+            @NonNull final OverlayIdentifier[] overlays) {
+        for (OverlayIdentifier s : overlays) {
             for (String a : array) {
-                if (a.contains(s)) {
+                if (a.contains(s.getPackageName())) {
                     return false;
                 }
             }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index 636f4c8..8e4b9ef 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -58,9 +59,12 @@
     static final int MODE_SINGLE_OVERLAY = 1;
     static final int MODE_MULTIPLE_OVERLAYS = 2;
 
-    static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
-    static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
-    static final String FRAMEWORK_OVERLAY_PKG = "com.android.overlaytest.framework";
+    static final OverlayIdentifier APP_OVERLAY_ONE_PKG =
+            new OverlayIdentifier("com.android.overlaytest.app_overlay_one");
+    static final OverlayIdentifier APP_OVERLAY_TWO_PKG =
+            new OverlayIdentifier("com.android.overlaytest.app_overlay_two");
+    static final OverlayIdentifier FRAMEWORK_OVERLAY_PKG =
+            new OverlayIdentifier("com.android.overlaytest.framework");
 
     protected OverlayBaseTest(int mode) {
         mMode = mode;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
index 0b4f5e2..27d7342 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
@@ -16,14 +16,19 @@
 
 package com.android.overlaytest;
 
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_ONE_PKG;
+import static com.android.overlaytest.OverlayBaseTest.APP_OVERLAY_TWO_PKG;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.testng.Assert.assertThrows;
 
 import android.content.Context;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
 import android.content.om.OverlayManagerTransaction;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.UserHandle;
 
@@ -40,8 +45,6 @@
 @RunWith(JUnit4.class)
 @MediumTest
 public class TransactionTest {
-    static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
-    static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
 
     private Context mContext;
     private Resources mResources;
@@ -58,8 +61,8 @@
         mUserHandle = UserHandle.of(mUserId);
 
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{},
-                new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{},
+                new OverlayIdentifier[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
     }
 
     @Test
@@ -78,8 +81,8 @@
         List<OverlayInfo> ois =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois.size(), 2);
-        assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG);
-        assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG);
+        assertEquals(ois.get(0).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
+        assertEquals(ois.get(1).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
 
         OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_TWO_PKG, true)
@@ -92,8 +95,8 @@
         List<OverlayInfo> ois2 =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois2.size(), 2);
-        assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG);
-        assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG);
+        assertEquals(ois2.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+        assertEquals(ois2.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
 
         OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_TWO_PKG, false)
@@ -105,8 +108,8 @@
         List<OverlayInfo> ois3 =
                 mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
         assertEquals(ois3.size(), 2);
-        assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG);
-        assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG);
+        assertEquals(ois3.get(0).getOverlayIdentifier(), APP_OVERLAY_TWO_PKG);
+        assertEquals(ois3.get(1).getOverlayIdentifier(), APP_OVERLAY_ONE_PKG);
     }
 
     @Test
@@ -116,7 +119,7 @@
 
         OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
                 .setEnabled(APP_OVERLAY_ONE_PKG, true)
-                .setEnabled("does-not-exist", true)
+                .setEnabled(new OverlayIdentifier("does-not-exist"), true)
                 .setEnabled(APP_OVERLAY_TWO_PKG, true)
                 .build();
         assertThrows(SecurityException.class, () -> mOverlayManager.commit(t));
@@ -125,9 +128,10 @@
         assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
     }
 
-    private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) {
-        final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId));
+    private void assertOverlayIsEnabled(final OverlayIdentifier overlay, boolean enabled,
+            int userId) {
+        final OverlayInfo oi = mOverlayManager.getOverlayInfo(overlay, UserHandle.of(userId));
         assertNotNull(oi);
-        assertEquals(oi.isEnabled(), enabled);
+        assertEquals(enabled, oi.isEnabled());
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index 420f755..5587203 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
     @BeforeClass
     public static void enableOverlay() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG},
-                new String[]{});
+                new OverlayIdentifier[]{
+                        FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+                },
+                new OverlayIdentifier[]{});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index a86255e..d275433 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,7 @@
     @BeforeClass
     public static void enableOverlays() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
-                new String[]{APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
+                new OverlayIdentifier[]{APP_OVERLAY_TWO_PKG});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index 51c4118..72cba8b 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -16,6 +16,8 @@
 
 package com.android.overlaytest;
 
+import android.content.om.OverlayIdentifier;
+
 import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
@@ -32,7 +34,9 @@
     @BeforeClass
     public static void disableOverlays() throws Exception {
         LocalOverlayManager.toggleOverlaysAndWait(
-                new String[]{},
-                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+                new OverlayIdentifier[]{},
+                new OverlayIdentifier[]{
+                        FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG
+                });
     }
 }
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
index 38e5fa1..926b186 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
@@ -25,7 +25,6 @@
     <item target="integer/matrix_100100" value="@integer/matrix_100100"/>
     <item target="integer/matrix_100101" value="@integer/matrix_100101"/>
     <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
-    <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
     <item target="integer/matrix_100111" value="@integer/matrix_100111"/>
     <item target="integer/matrix_101000" value="@integer/matrix_101000"/>
     <item target="integer/matrix_101001" value="@integer/matrix_101001"/>
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 65d3a01..549e074 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -6,7 +6,6 @@
 jsharkey@android.com
 jsharkey@google.com
 lorenzo@google.com
-moltmann@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
 toddke@android.com
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index cf0199b..c940574 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -26,6 +26,7 @@
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
@@ -40,10 +41,12 @@
         <permission name="android.permission.MOVE_PACKAGE"/>
         <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/etc/car/com.android.car.provision.xml b/data/etc/car/com.android.car.provision.xml
index 4fd9cae..42cfd3c 100644
--- a/data/etc/car/com.android.car.provision.xml
+++ b/data/etc/car/com.android.car.provision.xml
@@ -17,6 +17,7 @@
 <permissions>
     <privapp-permissions package="com.android.car.provision">
         <permission name="android.car.permission.CAR_POWERTRAIN"/>
+        <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.MANAGE_USERS"/>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index 19548bc..48f6ab3 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -25,6 +25,7 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.READ_LOGS"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 3a20a9c..bd30d7a 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -27,8 +27,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
         <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
         <permission name="android.permission.LOCATION_HARDWARE"/>
+        <permission name="android.permission.LOCK_DEVICE"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MASTER_CLEAR"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
@@ -40,9 +42,11 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RESET_PASSWORD"/>
         <permission name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"/>
         <!-- use for CarServiceTest -->
         <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
+        <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
 
         <!-- use for rotary fragment to enable/disable packages related to rotary -->
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 734561c..fa92b6d 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,6 +19,7 @@
         <!-- Required to place emergency calls from emergency info screen. -->
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.provision.xml b/data/etc/com.android.provision.xml
index d2ea0ec..68f8298 100644
--- a/data/etc/com.android.provision.xml
+++ b/data/etc/com.android.provision.xml
@@ -17,7 +17,7 @@
 <permissions>
     <privapp-permissions package="com.android.provision">
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
-        <permissionn ame="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
+        <permission name="android.permission.DISPATCH_PROVISIONING_MESSAGE"/>
         <permission name="android.permission.MASTER_CLEAR"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e473c55..3fdb0da 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -58,5 +58,6 @@
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0484a9a..fe4d0cf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -316,6 +316,8 @@
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.ACCESS_LOWPAN_STATE"/>
         <permission name="android.permission.BACKUP"/>
+        <!-- Needed for test only -->
+        <permission name="android.permission.BATTERY_PREDICTION"/>
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
@@ -474,6 +476,8 @@
         <!-- Permission required for GTS test - GtsAssistIntentTestCases -->
         <permission name="android.permission.MANAGE_SOUND_TRIGGER" />
         <permission name="android.permission.CAPTURE_AUDIO_HOTWORD" />
+        <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+        <permission name="android.permission.SIGNAL_REBOOT_READINESS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 222c9bd..ded4a27 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -811,12 +811,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-1144293044": {
-      "message": "SURFACE SET FREEZE LAYER: %s",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
-    },
     "-1142279614": {
       "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
       "level": "VERBOSE",
@@ -895,12 +889,6 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
-    "-1088782910": {
-      "message": "Translucent=%s Floating=%s ShowWallpaper=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1076978367": {
       "message": "thawRotation: mRotation=%d",
       "level": "VERBOSE",
@@ -1261,12 +1249,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
     },
-    "-639305784": {
-      "message": "Could not report config changes to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowToken.java"
-    },
     "-639217716": {
       "message": "setFocusedApp %s displayId=%d Callers=%s",
       "level": "INFO",
@@ -1417,12 +1399,6 @@
       "group": "WM_DEBUG_KEEP_SCREEN_ON",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "-477481651": {
-      "message": "SURFACE DESTROY PENDING: %s. %s",
-      "level": "INFO",
-      "group": "WM_SHOW_SURFACE_ALLOC",
-      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
-    },
     "-463348344": {
       "message": "Removing and adding activity %s to root task at top callers=%s",
       "level": "INFO",
@@ -1693,6 +1669,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-124316973": {
+      "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-118786523": {
       "message": "Resume failed; resetting state to %s: %s",
       "level": "VERBOSE",
@@ -1903,12 +1885,6 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "123161180": {
-      "message": "SEVER CHILDREN",
-      "level": "INFO",
-      "group": "WM_SHOW_TRANSACTIONS",
-      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
-    },
     "140319294": {
       "message": "IME target changed within ActivityRecord",
       "level": "DEBUG",
@@ -2569,12 +2545,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "838570988": {
-      "message": "Could not report token removal to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowToken.java"
-    },
     "872933199": {
       "message": "Changing focus from %s to %s displayId=%d Callers=%s",
       "level": "DEBUG",
@@ -3235,6 +3205,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
     },
+    "1699269281": {
+      "message": "Don't organize or trigger events for untrusted displayId=%d",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "1720229827": {
       "message": "Creating animation bounds layer",
       "level": "INFO",
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index cb4dd9e..88cf96a 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -39,6 +39,7 @@
 import android.view.NativeVectorDrawableAnimator;
 import android.view.PixelCopy;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.animation.AnimationUtils;
 
@@ -46,7 +47,6 @@
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Optional;
 import java.util.concurrent.Executor;
 import java.util.stream.Stream;
@@ -315,6 +315,16 @@
     }
 
     /**
+     * Sets the SurfaceControl to be used internally inside render thread
+     * @hide
+     * @param surfaceControl The surface control to pass to render thread in hwui.
+     *        If null, any previous references held in render thread will be discarded.
+    */
+    public void setSurfaceControl(@Nullable SurfaceControl surfaceControl) {
+        nSetSurfaceControl(mNativeProxy, surfaceControl != null ? surfaceControl.mNativeObject : 0);
+    }
+
+    /**
      * Sets the parameters that can be used to control a render request for a
      * {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
      * than a single frame request.
@@ -1148,24 +1158,14 @@
                             // Default to SRGB if the display doesn't support wide color
                             .orElse(Dataspace.SRGB);
 
-            float maxRefreshRate =
-                    (float) Arrays.stream(display.getSupportedModes())
-                            .mapToDouble(Mode::getRefreshRate)
-                            .max()
-                            .orElseGet(() -> {
-                                Log.i(LOG_TAG, "Failed to find the maximum display refresh rate");
-                                // Assume that the max refresh rate is 60hz if we can't find one.
-                                return 60.0;
-                            });
             // Grab the physical screen dimensions from the active display mode
             // Strictly speaking the screen resolution may not always be constant - it is for
             // sizing the font cache for the underlying rendering thread. Since it's a
             // heuristic we don't need to be always 100% correct.
             Mode activeMode = display.getMode();
             nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
-                    display.getRefreshRate(), maxRefreshRate,
-                    wideColorDataspace.mNativeDataspace, display.getAppVsyncOffsetNanos(),
-                    display.getPresentationDeadlineNanos());
+                    display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
+                    display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
 
             // Defensively clear out the context
             mContext = null;
@@ -1227,6 +1227,8 @@
 
     private static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
 
+    private static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl);
+
     private static native boolean nPause(long nativeProxy);
 
     private static native void nSetStopped(long nativeProxy, boolean stopped);
@@ -1324,6 +1326,5 @@
     private static native void nSetDisplayDensityDpi(int densityDpi);
 
     private static native void nInitDisplayInfo(int width, int height, float refreshRate,
-            float maxRefreshRate, int wideColorDataspace, long appVsyncOffsetNanos,
-            long presentationDeadlineNanos);
+            int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos);
 }
diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java
index cf2f970..25f76f6 100644
--- a/graphics/java/android/graphics/Point.java
+++ b/graphics/java/android/graphics/Point.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -96,6 +97,27 @@
     }
 
     /**
+     * @return Returns a {@link String} that represents this point which can be parsed with
+     * {@link #unflattenFromString(String)}.
+     * @hide
+     */
+    @NonNull
+    public String flattenToString() {
+        return x + "x" + y;
+    }
+
+    /**
+     * @return Returns a {@link Point} from a short string created from {@link #flattenToString()}.
+     * @hide
+     */
+    @Nullable
+    public static Point unflattenFromString(String s) throws NumberFormatException {
+        final int sep_ix = s.indexOf("x");
+        return new Point(Integer.parseInt(s.substring(0, sep_ix)),
+                Integer.parseInt(s.substring(sep_ix + 1)));
+    }
+
+    /**
      * Parcelable interface methods
      */
     @Override
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 35e6b859..32c777c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1346,6 +1346,19 @@
         }
     }
 
+    static {
+        // Preload Roboto-Regular.ttf in Zygote for improving app launch performance.
+        // TODO: add new attribute to fonts.xml to preload fonts in Zygote.
+        preloadFontFile("/system/fonts/Roboto-Regular.ttf");
+    }
+
+    private static void preloadFontFile(String filePath) {
+        File file = new File(filePath);
+        if (file.exists()) {
+            nativeWarmUpCache(filePath);
+        }
+    }
+
     /** @hide */
     @VisibleForTesting
     public static void destroySystemFontMap() {
@@ -1414,6 +1427,16 @@
         return Arrays.binarySearch(mSupportedAxes, axis) >= 0;
     }
 
+    /** @hide */
+    public List<FontFamily> getFallback() {
+        ArrayList<FontFamily> families = new ArrayList<>();
+        int familySize = nativeGetFamilySize(native_instance);
+        for (int i = 0; i < familySize; ++i) {
+            families.add(new FontFamily(nativeGetFamily(native_instance, i)));
+        }
+        return families;
+    }
+
     private static native long nativeCreateFromTypeface(long native_instance, int style);
     private static native long nativeCreateFromTypefaceWithExactStyle(
             long native_instance, int weight, boolean italic);
@@ -1439,6 +1462,13 @@
     @CriticalNative
     private static native long nativeGetReleaseFunc();
 
+    @CriticalNative
+    private static native int nativeGetFamilySize(long naitvePtr);
+
+    @CriticalNative
+    private static native long nativeGetFamily(long nativePtr, int index);
+
+
     private static native void nativeRegisterGenericFamily(String str, long nativePtr);
 
     private static native int nativeWriteTypefaces(
@@ -1447,4 +1477,6 @@
     private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer);
 
     private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
+
+    private static native void nativeWarmUpCache(String fileName);
 }
diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
new file mode 100644
index 0000000..80f65f9
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java
@@ -0,0 +1,284 @@
+/*
+ * 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.graphics.drawable;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.animation.RenderNodeAnimator;
+import android.util.ArraySet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public final class RippleAnimationSession {
+    private static final int ENTER_ANIM_DURATION = 350;
+    private static final int EXIT_ANIM_OFFSET = ENTER_ANIM_DURATION;
+    private static final int EXIT_ANIM_DURATION = 350;
+    private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    // Matches R.interpolator.fast_out_slow_in but as we have no context we can't just import that
+    private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+    private Consumer<RippleAnimationSession> mOnSessionEnd;
+    private AnimationProperties<Float, Paint> mProperties;
+    private AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>> mCanvasProperties;
+    private Runnable mOnUpdate;
+    private long mStartTime;
+    private boolean mForceSoftware;
+    private ArraySet<Animator> mActiveAnimations = new ArraySet(3);
+
+    RippleAnimationSession(@NonNull AnimationProperties<Float, Paint> properties,
+            boolean forceSoftware) {
+        mProperties = properties;
+        mForceSoftware = forceSoftware;
+    }
+
+    void end() {
+        for (Animator anim: mActiveAnimations) {
+            if (anim != null) anim.end();
+        }
+        mActiveAnimations.clear();
+    }
+
+    @NonNull RippleAnimationSession enter(Canvas canvas) {
+        if (isHwAccelerated(canvas)) {
+            enterHardware((RecordingCanvas) canvas);
+        } else {
+            enterSoftware();
+        }
+        mStartTime = System.nanoTime();
+        return this;
+    }
+
+    @NonNull RippleAnimationSession exit(Canvas canvas) {
+        if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas);
+        else exitSoftware();
+        return this;
+    }
+
+    private void onAnimationEnd(Animator anim) {
+        mActiveAnimations.remove(anim);
+    }
+
+    @NonNull RippleAnimationSession setOnSessionEnd(
+            @Nullable Consumer<RippleAnimationSession> onSessionEnd) {
+        mOnSessionEnd = onSessionEnd;
+        return this;
+    }
+
+    RippleAnimationSession setOnAnimationUpdated(@Nullable Runnable run) {
+        mOnUpdate = run;
+        mProperties.setOnChange(mOnUpdate);
+        return this;
+    }
+
+    private boolean isHwAccelerated(Canvas canvas) {
+        return canvas.isHardwareAccelerated() && !mForceSoftware;
+    }
+
+    private void exitSoftware() {
+        ValueAnimator expand = ValueAnimator.ofFloat(.5f, 1f);
+        expand.setDuration(EXIT_ANIM_DURATION);
+        expand.setStartDelay(computeDelay());
+        expand.addUpdateListener(updatedAnimation -> {
+            notifyUpdate();
+            mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+        });
+        expand.addListener(new AnimatorListener(this) {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
+                if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
+            }
+        });
+        expand.setInterpolator(LINEAR_INTERPOLATOR);
+        expand.start();
+        mActiveAnimations.add(expand);
+    }
+
+    private long computeDelay() {
+        long currentTime = System.nanoTime();
+        long timePassed =  (currentTime - mStartTime) / 1_000_000;
+        long difference = EXIT_ANIM_OFFSET;
+        return Math.max(difference - timePassed, 0);
+    }
+    private void notifyUpdate() {
+        Runnable onUpdate = mOnUpdate;
+        if (onUpdate != null) onUpdate.run();
+    }
+
+    RippleAnimationSession setForceSoftwareAnimation(boolean forceSw) {
+        mForceSoftware = forceSw;
+        return this;
+    }
+
+
+    private void exitHardware(RecordingCanvas canvas) {
+        AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
+                props = getCanvasProperties();
+        RenderNodeAnimator exit =
+                new RenderNodeAnimator(props.getProgress(), 1f);
+        exit.setDuration(EXIT_ANIM_DURATION);
+        exit.addListener(new AnimatorListener(this) {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                Consumer<RippleAnimationSession> onEnd = mOnSessionEnd;
+                if (onEnd != null) onEnd.accept(RippleAnimationSession.this);
+            }
+        });
+        exit.setTarget(canvas);
+        exit.setInterpolator(DECELERATE_INTERPOLATOR);
+
+        long delay = computeDelay();
+        exit.setStartDelay(delay);
+        exit.start();
+        mActiveAnimations.add(exit);
+    }
+
+    private void enterHardware(RecordingCanvas can) {
+        AnimationProperties<CanvasProperty<Float>, CanvasProperty<Paint>>
+                props = getCanvasProperties();
+        RenderNodeAnimator expand =
+                new RenderNodeAnimator(props.getProgress(), .5f);
+        expand.setTarget(can);
+        expand.setDuration(ENTER_ANIM_DURATION);
+        expand.addListener(new AnimatorListener(this));
+        expand.setInterpolator(LINEAR_INTERPOLATOR);
+        expand.start();
+        mActiveAnimations.add(expand);
+    }
+
+    private void enterSoftware() {
+        ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f);
+        expand.addUpdateListener(updatedAnimation -> {
+            notifyUpdate();
+            mProperties.getShader().setProgress((Float) expand.getAnimatedValue());
+        });
+        expand.addListener(new AnimatorListener(this));
+        expand.setInterpolator(LINEAR_INTERPOLATOR);
+        expand.start();
+        mActiveAnimations.add(expand);
+    }
+
+    @NonNull AnimationProperties<Float, Paint> getProperties() {
+        return mProperties;
+    }
+
+    @NonNull AnimationProperties getCanvasProperties() {
+        if (mCanvasProperties == null) {
+            mCanvasProperties = new AnimationProperties<>(
+                    CanvasProperty.createFloat(mProperties.getX()),
+                    CanvasProperty.createFloat(mProperties.getY()),
+                    CanvasProperty.createFloat(mProperties.getMaxRadius()),
+                    CanvasProperty.createPaint(mProperties.getPaint()),
+                    CanvasProperty.createFloat(mProperties.getProgress()),
+                    mProperties.getShader());
+        }
+        return mCanvasProperties;
+    }
+
+    private static class AnimatorListener implements Animator.AnimatorListener {
+        private final RippleAnimationSession mSession;
+
+        AnimatorListener(RippleAnimationSession session) {
+            mSession = session;
+        }
+        @Override
+        public void onAnimationStart(Animator animation) {
+
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mSession.onAnimationEnd(animation);
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+
+        }
+    }
+
+    static class AnimationProperties<FloatType, PaintType> {
+        private final FloatType mY;
+        private FloatType mProgress;
+        private FloatType mMaxRadius;
+        private final PaintType mPaint;
+        private final FloatType mX;
+        private final RippleShader mShader;
+        private Runnable mOnChange;
+
+        private void onChange() {
+            if (mOnChange != null) mOnChange.run();
+        }
+
+        private void setOnChange(Runnable onChange) {
+            mOnChange = onChange;
+        }
+
+        AnimationProperties(FloatType x, FloatType y, FloatType maxRadius,
+                PaintType paint, FloatType progress, RippleShader shader) {
+            mY = y;
+            mX = x;
+            mMaxRadius = maxRadius;
+            mPaint = paint;
+            mShader = shader;
+            mProgress = progress;
+        }
+
+        FloatType getProgress() {
+            return mProgress;
+        }
+
+        FloatType getX() {
+            return mX;
+        }
+
+        FloatType getY() {
+            return mY;
+        }
+
+        FloatType getMaxRadius() {
+            return mMaxRadius;
+        }
+
+        PaintType getPaint() {
+            return mPaint;
+        }
+
+        RippleShader getShader() {
+            return mShader;
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index bab80ce..5024875 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,14 @@
 
 package android.graphics.drawable;
 
+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.RetentionPolicy.SOURCE;
+
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -27,17 +35,21 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.view.animation.LinearInterpolator;
 
 import com.android.internal.R;
 
@@ -45,6 +57,9 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -94,6 +109,17 @@
  * </pre>
  *
  * @attr ref android.R.styleable#RippleDrawable_color
+ *
+ * To change the ripple style, assign the value of "solid" or "patterned" to the android:rippleStyle
+ * attribute.
+ *
+ * <pre>
+ * <code>&lt;!-- A red ripple masked against an opaque rectangle. --/>
+ * &lt;ripple android:rippleStyle="patterned">
+ * &lt;/ripple></code>
+ * </pre>
+ *
+ * @attr ref android.R.styleable#RippleDrawable_rippleStyle
  */
 public class RippleDrawable extends LayerDrawable {
     /**
@@ -102,6 +128,29 @@
      */
     public static final int RADIUS_AUTO = -1;
 
+    /**
+     * Ripple style where a solid circle is drawn. This is also the default style
+     * @see #setRippleStyle(int)
+     */
+    public static final int STYLE_SOLID = 0;
+    /**
+     * Ripple style where a circle shape with a patterned,
+     * noisy interior expands from the hotspot to the bounds".
+     * @see #setRippleStyle(int)
+     */
+    public static final int STYLE_PATTERNED = 1;
+
+    /**
+     * Ripple drawing style
+     * @hide
+     */
+    @Retention(SOURCE)
+    @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+    @IntDef({STYLE_SOLID, STYLE_PATTERNED})
+    public @interface RippleStyle {
+    }
+
+    private static final int BACKGROUND_OPACITY_DURATION = 80;
     private static final int MASK_UNKNOWN = -1;
     private static final int MASK_NONE = 0;
     private static final int MASK_CONTENT = 1;
@@ -109,6 +158,7 @@
 
     /** The maximum number of ripples supported. */
     private static final int MAX_RIPPLES = 10;
+    private static final LinearInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
     private final Rect mTempRect = new Rect();
 
@@ -172,6 +222,14 @@
      */
     private boolean mForceSoftware;
 
+    // Patterned
+    private float mTargetBackgroundOpacity;
+    private ValueAnimator mBackgroundAnimation;
+    private float mBackgroundOpacity;
+    private boolean mRunBackgroundAnimation;
+    private boolean mExitingAnimation;
+    private ArrayList<RippleAnimationSession> mRunningAnimations = new ArrayList<>();
+
     /**
      * Constructor used for drawable inflation.
      */
@@ -235,7 +293,7 @@
             Arrays.fill(ripples, 0, count, null);
         }
         mExitingRipplesCount = 0;
-
+        mExitingAnimation = true;
         // Always draw an additional "clean" frame after canceling animations.
         invalidateSelf(false);
     }
@@ -276,21 +334,37 @@
     private void setRippleActive(boolean active) {
         if (mRippleActive != active) {
             mRippleActive = active;
+        }
+        if (mState.mRippleStyle == STYLE_SOLID) {
             if (active) {
                 tryRippleEnter();
             } else {
                 tryRippleExit();
             }
+        } else {
+            if (active) {
+                startPatternedAnimation();
+            } else {
+                exitPatternedAnimation();
+            }
         }
     }
 
     private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
-        if (mBackground == null && (hovered || focused)) {
-            mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
-            mBackground.setup(mState.mMaxRadius, mDensity);
-        }
-        if (mBackground != null) {
-            mBackground.setState(focused, hovered, pressed);
+        if (mState.mRippleStyle == STYLE_SOLID) {
+            if (mBackground == null && (hovered || focused)) {
+                mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+                mBackground.setup(mState.mMaxRadius, mDensity);
+            }
+            if (mBackground != null) {
+                mBackground.setState(focused, hovered, pressed);
+            }
+        } else {
+            if (focused || hovered) {
+                enterPatternedBackgroundAnimation(focused, hovered);
+            } else {
+                exitPatternedBackgroundAnimation();
+            }
         }
     }
 
@@ -317,6 +391,9 @@
             mRipple.onBoundsChange();
         }
 
+        mState.mMaxRadius = mState.mMaxRadius <= 0 && mState.mRippleStyle != STYLE_SOLID
+                ? (int) computeRadius()
+                : mState.mMaxRadius;
         invalidateSelf();
     }
 
@@ -330,7 +407,11 @@
             // If we just became visible, ensure the background and ripple
             // visibilities are consistent with their internal states.
             if (mRippleActive) {
-                tryRippleEnter();
+                if (mState.mRippleStyle == STYLE_SOLID) {
+                    tryRippleEnter();
+                } else {
+                    invalidateSelf();
+                }
             }
 
             // Skip animations, just show the correct final states.
@@ -489,6 +570,8 @@
 
         mState.mMaxRadius = a.getDimensionPixelSize(
                 R.styleable.RippleDrawable_radius, mState.mMaxRadius);
+
+        mState.mRippleStyle = a.getInteger(R.styleable.RippleDrawable_rippleStyle, STYLE_SOLID);
     }
 
     private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
@@ -535,9 +618,9 @@
 
     @Override
     public void setHotspot(float x, float y) {
+        mPendingX = x;
+        mPendingY = y;
         if (mRipple == null || mBackground == null) {
-            mPendingX = x;
-            mPendingY = y;
             mHasPending = true;
         }
 
@@ -665,6 +748,14 @@
      */
     @Override
     public void draw(@NonNull Canvas canvas) {
+        if (mState.mRippleStyle == STYLE_SOLID) {
+            drawSolid(canvas);
+        } else {
+            drawPatterned(canvas);
+        }
+    }
+
+    private void drawSolid(Canvas canvas) {
         pruneRipples();
 
         // Clip to the dirty bounds, which will be the drawable bounds if we
@@ -681,6 +772,178 @@
         canvas.restoreToCount(saveCount);
     }
 
+    private void exitPatternedBackgroundAnimation() {
+        mTargetBackgroundOpacity = 0;
+        if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
+        // after cancel
+        mRunBackgroundAnimation = true;
+        invalidateSelf(false);
+    }
+
+    private void startPatternedAnimation() {
+        mRippleActive = true;
+        invalidateSelf(false);
+    }
+
+    private void exitPatternedAnimation() {
+        mExitingAnimation = true;
+        invalidateSelf(false);
+    }
+
+    private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) {
+        mBackgroundOpacity = 0;
+        mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f;
+        if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
+        // after cancel
+        mRunBackgroundAnimation = true;
+        invalidateSelf(false);
+    }
+
+    private void startBackgroundAnimation() {
+        mRunBackgroundAnimation = false;
+        mBackgroundAnimation = ValueAnimator.ofFloat(mBackgroundOpacity, mTargetBackgroundOpacity);
+        mBackgroundAnimation.setInterpolator(LINEAR_INTERPOLATOR);
+        mBackgroundAnimation.setDuration(BACKGROUND_OPACITY_DURATION);
+        mBackgroundAnimation.addUpdateListener(update -> {
+            mBackgroundOpacity = (float) update.getAnimatedValue();
+            invalidateSelf(false);
+        });
+        mBackgroundAnimation.start();
+    }
+
+    private void drawPatterned(@NonNull Canvas canvas) {
+        final Rect bounds = getBounds();
+        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+        boolean useCanvasProps = shouldUseCanvasProps(canvas);
+        boolean changedHotspotBounds = !bounds.equals(mHotspotBounds);
+        if (isBounded()) {
+            canvas.clipRect(mHotspotBounds);
+        }
+        float x, y;
+        if (changedHotspotBounds) {
+            x = mHotspotBounds.exactCenterX();
+            y = mHotspotBounds.exactCenterY();
+            useCanvasProps = false;
+        } else {
+            x = mPendingX;
+            y = mPendingY;
+        }
+        boolean shouldAnimate = mRippleActive;
+        boolean shouldExit = mExitingAnimation;
+        mRippleActive = false;
+        mExitingAnimation = false;
+        getRipplePaint();
+        drawContent(canvas);
+        drawPatternedBackground(canvas);
+        if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
+            RippleAnimationSession.AnimationProperties<Float, Paint> properties =
+                    createAnimationProperties(x, y);
+            mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps)
+                    .setOnAnimationUpdated(useCanvasProps ? null : () -> invalidateSelf(false))
+                    .setOnSessionEnd(session -> {
+                        mRunningAnimations.remove(session);
+                    })
+                    .setForceSoftwareAnimation(!useCanvasProps)
+                    .enter(canvas));
+        }
+        if (shouldExit) {
+            for (int i = 0; i < mRunningAnimations.size(); i++) {
+                RippleAnimationSession s = mRunningAnimations.get(i);
+                s.exit(canvas);
+            }
+        }
+        for (int i = 0; i < mRunningAnimations.size(); i++) {
+            RippleAnimationSession s = mRunningAnimations.get(i);
+            if (useCanvasProps) {
+                RippleAnimationSession.AnimationProperties<CanvasProperty<Float>,
+                        CanvasProperty<Paint>>
+                        p = s.getCanvasProperties();
+                RecordingCanvas can = (RecordingCanvas) canvas;
+                can.drawRipple(p.getX(), p.getY(), p.getMaxRadius(), p.getPaint(),
+                        p.getProgress(), p.getShader());
+            } else {
+                RippleAnimationSession.AnimationProperties<Float, Paint> p =
+                        s.getProperties();
+                float posX, posY;
+                if (changedHotspotBounds) {
+                    posX = x;
+                    posY = y;
+                    if (p.getPaint().getShader() instanceof RippleShader) {
+                        RippleShader shader = (RippleShader) p.getPaint().getShader();
+                        shader.setOrigin(posX, posY);
+                    }
+                } else {
+                    posX = p.getX();
+                    posY = p.getY();
+                }
+                float radius = p.getMaxRadius();
+                canvas.drawCircle(posX, posY, radius, p.getPaint());
+            }
+        }
+        canvas.restoreToCount(saveCount);
+    }
+
+    private void drawPatternedBackground(Canvas c) {
+        if (mRunBackgroundAnimation) {
+            startBackgroundAnimation();
+        }
+        if (mBackgroundOpacity == 0) return;
+        Paint p = mRipplePaint;
+        float newOpacity = mBackgroundOpacity;
+        final int origAlpha = p.getAlpha();
+        final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
+        if (alpha > 0) {
+            ColorFilter origFilter = p.getColorFilter();
+            p.setColorFilter(mMaskColorFilter);
+            p.setAlpha(alpha);
+            Rect b = mHotspotBounds;
+            c.drawCircle(b.centerX(), b.centerY(), mState.mMaxRadius, p);
+            p.setAlpha(origAlpha);
+            p.setColorFilter(origFilter);
+        }
+    }
+
+    private float computeRadius() {
+        Rect b = getDirtyBounds();
+        float gap = 0;
+        float radius = (float) Math.sqrt(b.width() * b.width() + b.height() * b.height()) / 2 + gap;
+        return radius;
+    }
+
+    @NonNull
+    private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
+            float x, float y) {
+        Paint p = new Paint(mRipplePaint);
+        float radius = mState.mMaxRadius;
+        RippleAnimationSession.AnimationProperties<Float, Paint> properties;
+        RippleShader shader = new RippleShader();
+        int color = mMaskColorFilter == null
+                ? mState.mColor.getColorForState(getState(), Color.BLACK)
+                : mMaskColorFilter.getColor();
+        color = color | 0xFF000000;
+        shader.setColor(color);
+        shader.setOrigin(x, y);
+        shader.setRadius(radius);
+        shader.setProgress(.0f);
+        properties = new RippleAnimationSession.AnimationProperties<>(
+                x, y, radius, p, 0f,
+                shader);
+        if (mMaskShader == null) {
+            shader.setHasMask(false);
+        } else {
+            shader.setShader(mMaskShader);
+            shader.setHasMask(true);
+        }
+        p.setShader(shader);
+        p.setColorFilter(null);
+        p.setColor(color);
+        return properties;
+    }
+
+    private boolean shouldUseCanvasProps(Canvas c) {
+        return !mForceSoftware && c.isHardwareAccelerated();
+    }
+
     @Override
     public void invalidateSelf() {
         invalidateSelf(true);
@@ -774,18 +1037,23 @@
         // Draw the appropriate mask anchored to (0,0).
         final int left = bounds.left;
         final int top = bounds.top;
-        mMaskCanvas.translate(-left, -top);
+        if (mState.mRippleStyle == STYLE_SOLID) {
+            mMaskCanvas.translate(-left, -top);
+        }
         if (maskType == MASK_EXPLICIT) {
             drawMask(mMaskCanvas);
         } else if (maskType == MASK_CONTENT) {
             drawContent(mMaskCanvas);
         }
-        mMaskCanvas.translate(left, top);
+        if (mState.mRippleStyle == STYLE_SOLID) {
+            mMaskCanvas.translate(left, top);
+        }
     }
 
     private int getMaskType() {
         if (mRipple == null && mExitingRipplesCount <= 0
-                && (mBackground == null || !mBackground.isVisible())) {
+                && (mBackground == null || !mBackground.isVisible())
+                && mState.mRippleStyle == STYLE_SOLID) {
             // We might need a mask later.
             return MASK_UNKNOWN;
         }
@@ -874,7 +1142,7 @@
         updateMaskShaderIfNeeded();
 
         // Position the shader to account for canvas translation.
-        if (mMaskShader != null) {
+        if (mMaskShader != null && mState.mRippleStyle == STYLE_SOLID) {
             final Rect bounds = getBounds();
             mMaskMatrix.setTranslate(bounds.left - x, bounds.top - y);
             mMaskShader.setLocalMatrix(mMaskMatrix);
@@ -973,6 +1241,31 @@
         return this;
     }
 
+    /**
+     * Sets the visual style of the ripple.
+     *
+     * @see #STYLE_SOLID
+     * @see #STYLE_PATTERNED
+     *
+     * @param style The style of the ripple
+     */
+    public void setRippleStyle(@RippleStyle int style) throws IllegalArgumentException {
+        if (style == STYLE_SOLID || style == STYLE_PATTERNED) {
+            mState.mRippleStyle = style;
+        } else {
+            throw new IllegalArgumentException("Invalid style value " + style);
+        }
+    }
+
+    /**
+     * Get the current ripple style
+     * @return Ripple style
+     */
+    public @RippleStyle int getRippleStyle() {
+        return mState.mRippleStyle;
+    }
+
+
     @Override
     RippleState createConstantState(LayerState state, Resources res) {
         return new RippleState(state, this, res);
@@ -983,6 +1276,7 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
         int mMaxRadius = RADIUS_AUTO;
+        int mRippleStyle = STYLE_SOLID;
 
         public RippleState(LayerState orig, RippleDrawable owner, Resources res) {
             super(orig, owner, res);
@@ -992,6 +1286,7 @@
                 mTouchThemeAttrs = origs.mTouchThemeAttrs;
                 mColor = origs.mColor;
                 mMaxRadius = origs.mMaxRadius;
+                mRippleStyle = origs.mRippleStyle;
 
                 if (origs.mDensity != mDensity) {
                     applyDensityScaling(orig.mDensity, mDensity);
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
new file mode 100644
index 0000000..500efdd
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -0,0 +1,89 @@
+/*
+ * 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.graphics.drawable;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.graphics.RuntimeShader;
+import android.graphics.Shader;
+
+final class RippleShader extends RuntimeShader {
+    private static final String SHADER = "uniform float2 in_origin;\n"
+            + "uniform float in_maxRadius;\n"
+            + "uniform float in_progress;\n"
+            + "uniform float in_hasMask;\n"
+            + "uniform float4 in_color;\n"
+            + "uniform shader in_shader;\n"
+            + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + "
+            + "(pf.y - p0.y) * (pf.y - p0.y)); }\n"
+            + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n"
+            + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233))) * "
+            + "43758.5453123); }\n"
+            + "float4 main(float2 p)\n"
+            + "{\n"
+            + "    float fraction = in_progress;\n"
+            + "    float2 fragCoord = p;//sk_FragCoord.xy;\n"
+            + "    float maxDist = in_maxRadius;\n"
+            + "    float fragDist = dist2(in_origin, fragCoord.xy);\n"
+            + "    float circleRadius = maxDist * fraction;\n"
+            + "    float colorVal = (fragDist - circleRadius) / maxDist;\n"
+            + "    float d = fragDist < circleRadius \n"
+            + "        ? 1. - abs(colorVal * 3. * smoothstep(0., 1., fraction)) \n"
+            + "        : 1. - abs(colorVal * 5.);\n"
+            + "    d = smoothstep(0., 1., d);\n"
+            + "    float divider = 2.;\n"
+            + "    float x = floor(fragCoord.x / divider);\n"
+            + "    float y = floor(fragCoord.y / divider);\n"
+            + "    float density = .95;\n"
+            + "    d = rand(float2(x, y)) > density ? d : d * .2;\n"
+            + "    d = d * rand(float2(fraction, x * y));\n"
+            + "    float alpha = 1. - pow(fraction, 2.);\n"
+            + "    if (in_hasMask != 0.) {return sample(in_shader).a * in_color * d * alpha;}\n"
+            + "    return in_color * d * alpha;\n"
+            + "}\n";
+
+    RippleShader() {
+        super(SHADER, false);
+    }
+
+    public void setShader(@NonNull Shader s) {
+        setInputShader("in_shader", s);
+    }
+
+    public void setRadius(float radius) {
+        setUniform("in_maxRadius", radius);
+    }
+
+    public void setOrigin(float x, float y) {
+        setUniform("in_origin", new float[] {x, y});
+    }
+
+    public void setProgress(float progress) {
+        setUniform("in_progress", progress);
+    }
+
+    public void setHasMask(boolean hasMask) {
+        setUniform("in_hasMask", hasMask ? 1 : 0);
+    }
+
+    public void setColor(@ColorInt int colorIn) {
+        Color color = Color.valueOf(colorIn);
+        this.setUniform("in_color", new float[] {color.red(),
+                color.green(), color.blue(), color.alpha()});
+    }
+}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index b153c99..f826b24 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -27,7 +27,6 @@
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
-import android.util.LongSparseLongArray;
 import android.util.TypedValue;
 
 import com.android.internal.annotations.GuardedBy;
@@ -63,10 +62,9 @@
             NativeAllocationRegistry.createMalloced(
                     ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont());
 
-    private static final Object SOURCE_ID_LOCK = new Object();
-    @GuardedBy("SOURCE_ID_LOCK")
-    private static final LongSparseLongArray FONT_SOURCE_ID_MAP =
-            new LongSparseLongArray(300);  // System font has 200 fonts, so 300 should be enough.
+    private static final NativeAllocationRegistry FONT_REGISTRY =
+            NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(),
+                    nGetReleaseNativeFont());
 
     /**
      * A builder class for creating new Font.
@@ -519,18 +517,19 @@
     private @Nullable FontVariationAxis[] mAxes = null;
     @GuardedBy("mLock")
     private @NonNull LocaleList mLocaleList = null;
-    @GuardedBy("mLock")
-    private int mSourceIdentifier = -1;
 
     /**
      * Use Builder instead
      *
      * Caller must increment underlying minikin::Font ref count.
+     * This class takes the ownership of the passing native objects.
      *
      * @hide
      */
     public Font(long nativePtr) {
         mNativePtr = nativePtr;
+
+        FONT_REGISTRY.registerNativeAllocation(this, mNativePtr);
     }
 
     /**
@@ -751,20 +750,7 @@
      * @return an unique identifier for the font source data.
      */
     public int getSourceIdentifier() {
-        synchronized (mLock) {
-            if (mSourceIdentifier == -1) {
-                long bufferAddress = nGetBufferAddress(mNativePtr);
-                synchronized (SOURCE_ID_LOCK) {
-                    long id = FONT_SOURCE_ID_MAP.get(bufferAddress, -1);
-                    if (id == -1) {
-                        id = FONT_SOURCE_ID_MAP.size();
-                        FONT_SOURCE_ID_MAP.append(bufferAddress, id);
-                    }
-                    mSourceIdentifier = (int) id;
-                }
-            }
-            return mSourceIdentifier;
-        }
+        return nGetSourceId(mNativePtr);
     }
 
     /**
@@ -883,6 +869,9 @@
     private static native long nGetBufferAddress(long font);
 
     @CriticalNative
+    private static native int nGetSourceId(long font);
+
+    @CriticalNative
     private static native long nGetReleaseNativeFont();
 
     @FastNative
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 8c13d3e..a771a6e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -125,7 +125,7 @@
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
-            final FontFamily family = new FontFamily(mFonts, ptr);
+            final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
         }
@@ -146,7 +146,8 @@
     private final long mNativePtr;
 
     // Use Builder instead.
-    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
+    /** @hide */
+    public FontFamily(long ptr) {
         mNativePtr = ptr;
     }
 
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 7bd5817..d1fe2cd 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
@@ -189,6 +190,17 @@
         return TextUtils.join(",", axes);
     }
 
+    /**
+     * Stringify the array of FontVariationAxis.
+     * @hide
+     */
+    public static @NonNull String toFontVariationSettings(@Nullable List<FontVariationAxis> axes) {
+        if (axes == null) {
+            return "";
+        }
+        return TextUtils.join(",", axes);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o == this) {
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 904085f..255f9e6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -68,44 +68,23 @@
      */
     public static @NonNull Set<Font> getAvailableFonts() {
         synchronized (LOCK) {
-            if (sAvailableFonts != null) {
-                return sAvailableFonts;
-            }
-
-            if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
-                sAvailableFonts = collectAllFonts();
-            } else {
+            if (sAvailableFonts == null) {
                 Set<Font> set = new ArraySet<>();
-                for (FontFamily[] items : sFamilyMap.values()) {
-                    for (FontFamily family : items) {
-                        for (int i = 0; i < family.getSize(); ++i) {
-                            set.add(family.getFont(i));
+                for (Typeface tf : Typeface.getSystemFontMap().values()) {
+                    List<FontFamily> families = tf.getFallback();
+                    for (int i = 0; i < families.size(); ++i) {
+                        FontFamily family = families.get(i);
+                        for (int j = 0; j < family.getSize(); ++j) {
+                            set.add(family.getFont(j));
                         }
                     }
                 }
-
                 sAvailableFonts = Collections.unmodifiableSet(set);
             }
             return sAvailableFonts;
         }
     }
 
-    private static @NonNull Set<Font> collectAllFonts() {
-        // TODO: use updated fonts
-        FontConfig fontConfig = getSystemPreinstalledFontConfig();
-        Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
-
-        Set<Font> res = new ArraySet<>();
-        for (FontFamily[] families : map.values()) {
-            for (FontFamily family : families) {
-                for (int i = 0; i < family.getSize(); ++i) {
-                    res.add(family.getFont(i));
-                }
-            }
-        }
-        return res;
-    }
-
     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
         try (FileInputStream file = new FileInputStream(fullPath)) {
             final FileChannel fileChannel = file.getChannel();
@@ -329,13 +308,4 @@
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
-
-    /**
-     * @hide
-     */
-    public void resetFallbackMapping(Map<String, FontFamily[]> fallbackMap) {
-        synchronized (LOCK) {
-            sFamilyMap = fallbackMap;
-        }
-    }
 }
diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS
index f04e200..057dc0d 100644
--- a/graphics/java/android/graphics/pdf/OWNERS
+++ b/graphics/java/android/graphics/pdf/OWNERS
@@ -5,4 +5,3 @@
 sumir@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
-moltmann@google.com
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 334b111..988838b 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -17,6 +17,7 @@
 package android.security.keystore;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Build;
 import android.security.Credentials;
 import android.security.KeyPairGeneratorSpec;
@@ -25,6 +26,8 @@
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 
 import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
 import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
@@ -477,11 +480,11 @@
 
             success = true;
             return keyPair;
-        } catch (ProviderException e) {
+        } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) {
           if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
               throw new SecureKeyImportUnavailableException(e);
           } else {
-              throw e;
+                throw new ProviderException(e);
           }
         } finally {
             if (!success) {
@@ -491,7 +494,7 @@
     }
 
     private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
-            throws ProviderException {
+            throws ProviderException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
         if (challenge != null) {
             KeymasterArguments args = new KeymasterArguments();
@@ -510,6 +513,60 @@
                         Build.MODEL.getBytes(StandardCharsets.UTF_8));
             }
 
+            int[] idTypes = mSpec.getAttestationIds();
+            if (idTypes != null) {
+                final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+                for (int idType : idTypes) {
+                    idTypesSet.add(idType);
+                }
+                TelephonyManager telephonyService = null;
+                if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+                        || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+                    telephonyService =
+                            (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+                                    Context.TELEPHONY_SERVICE);
+                    if (telephonyService == null) {
+                        throw new DeviceIdAttestationException(
+                                "Unable to access telephony service");
+                    }
+                }
+                for (final Integer idType : idTypesSet) {
+                    switch (idType) {
+                        case AttestationUtils.ID_TYPE_SERIAL:
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+                                    Build.getSerial().getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        case AttestationUtils.ID_TYPE_IMEI: {
+                            final String imei = telephonyService.getImei(0);
+                            if (imei == null) {
+                                throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+                            }
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+                                    imei.getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        }
+                        case AttestationUtils.ID_TYPE_MEID: {
+                            final String meid = telephonyService.getMeid(0);
+                            if (meid == null) {
+                                throw new DeviceIdAttestationException("Unable to retrieve MEID");
+                            }
+                            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+                                    meid.getBytes(StandardCharsets.UTF_8)
+                            );
+                            break;
+                        }
+                        case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+                            args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+                            break;
+                        }
+                        default:
+                            throw new IllegalArgumentException("Unknown device ID type " + idType);
+                    }
+                }
+            }
+
             return getAttestationChain(privateKeyAlias, keyPair, args);
         }
 
@@ -547,7 +604,8 @@
         }
     }
 
-    private KeymasterArguments constructKeyGenerationArguments() {
+    private KeymasterArguments constructKeyGenerationArguments()
+            throws IllegalArgumentException, DeviceIdAttestationException {
         KeymasterArguments args = new KeymasterArguments();
         args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
         args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -565,9 +623,9 @@
                 mSpec.getKeyValidityForConsumptionEnd());
         addAlgorithmSpecificParameters(args);
 
-        if (mSpec.isUniqueIdIncluded())
+        if (mSpec.isUniqueIdIncluded()) {
             args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
-
+        }
         return args;
     }
 
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 16bf546..0871517 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -434,14 +434,16 @@
     @NonNull
     public static java.security.KeyStore getKeyStoreForUid(int uid)
             throws KeyStoreException, NoSuchProviderException {
-        String providerName = PROVIDER_NAME;
+        final java.security.KeyStore.LoadStoreParameter loadParameter;
         if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) {
-            providerName = "AndroidKeyStoreLegacy";
+            loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
+                    KeyProperties.legacyUidToNamespace(uid));
+        } else {
+            loadParameter = new AndroidKeyStoreLoadStoreParameter(uid);
         }
-        java.security.KeyStore result =
-                java.security.KeyStore.getInstance(providerName);
+        java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
         try {
-            result.load(new AndroidKeyStoreLoadStoreParameter(uid));
+            result.load(loadParameter);
         } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
             throw new KeyStoreException(
                     "Failed to load AndroidKeyStore KeyStore for UID " + uid, e);
diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java
index c8c1de4..f22b604 100644
--- a/keystore/java/android/security/keystore/ArrayUtils.java
+++ b/keystore/java/android/security/keystore/ArrayUtils.java
@@ -34,6 +34,14 @@
         return ((array != null) && (array.length > 0)) ? array.clone() : array;
     }
 
+    /**
+     * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the
+     * array.
+     */
+    public static int[] cloneIfNotEmpty(int[] array) {
+        return ((array != null) && (array.length > 0)) ? array.clone() : array;
+    }
+
     public static byte[] cloneIfNotEmpty(byte[] array) {
         return ((array != null) && (array.length > 0)) ? array.clone() : array;
     }
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c2a7b2e..2b0d7e5 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -236,6 +236,47 @@
  * keyStore.load(null);
  * key = (SecretKey) keyStore.getKey("key2", null);
  * }</pre>
+ *
+ * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3>
+ * This example illustrates how to generate an elliptic curve key pair, used to establish a shared
+ * secret with another party using ECDH key agreement.
+ * <pre> {@code
+ * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
+ *         KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ * keyPairGenerator.initialize(
+ *         new KeyGenParameterSpec.Builder(
+ *             "eckeypair",
+ *             KeyProperties.PURPOSE_AGREE_KEY)
+ *             .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+ *             .build());
+ * KeyPair myKeyPair = keyPairGenerator.generateKeyPair();
+ *
+ * // Exchange public keys with server. A new ephemeral key MUST be used for every message.
+ * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server.
+ *
+ * // Create a shared secret based on our private key and the other party's public key.
+ * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore");
+ * keyAgreement.init(myKeyPair.getPrivate());
+ * keyAgreement.doPhase(serverEphemeralPublicKey, true);
+ * byte[] sharedSecret = keyAgreement.generateSecret();
+ *
+ * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation
+ * // function with some other data: "salt" and "info". Salt is an optional random value,
+ * // omitted in this example. It's good practice to include both public keys and any other
+ * // key negotiation data in info. Here we use the public keys and a label that indicates
+ * // messages encrypted with this key are coming from the server.
+ * byte[] salt = {};
+ * ByteArrayOutputStream info = new ByteArrayOutputStream();
+ * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8));
+ * info.write(myKeyPair.getPublic().getEncoded());
+ * info.write(serverEphemeralPublicKey.getEncoded());
+ *
+ * // This example uses the Tink library and the HKDF key derivation function.
+ * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf(
+ *         "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32));
+ * byte[] associatedData = {};
+ * return key.decrypt(ciphertext, associatedData);
+ * }
  */
 public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs {
 
@@ -267,6 +308,7 @@
     private final boolean mUserPresenceRequired;
     private final byte[] mAttestationChallenge;
     private final boolean mDevicePropertiesAttestationIncluded;
+    private final int[] mAttestationIds;
     private final boolean mUniqueIdIncluded;
     private final boolean mUserAuthenticationValidWhileOnBody;
     private final boolean mInvalidatedByBiometricEnrollment;
@@ -308,6 +350,7 @@
             boolean userPresenceRequired,
             byte[] attestationChallenge,
             boolean devicePropertiesAttestationIncluded,
+            int[] attestationIds,
             boolean uniqueIdIncluded,
             boolean userAuthenticationValidWhileOnBody,
             boolean invalidatedByBiometricEnrollment,
@@ -361,6 +404,7 @@
         mUserAuthenticationType = userAuthenticationType;
         mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
         mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded;
+        mAttestationIds = attestationIds;
         mUniqueIdIncluded = uniqueIdIncluded;
         mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
@@ -720,6 +764,25 @@
     }
 
     /**
+     * @hide
+     * Allows the caller to specify device IDs to be attested to in the certificate for the
+     * generated key pair. These values are the enums specified in
+     * {@link android.security.keystore.AttestationUtils}
+     *
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+     * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+     * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+     *
+     * @return integer array representing the requested device IDs to attest.
+     */
+    @SystemApi
+    @Nullable
+    public int[] getAttestationIds() {
+        return Utils.cloneIfNotNull(mAttestationIds);
+    }
+
+    /**
      * @hide This is a system-only API
      *
      * Returns {@code true} if the attestation certificate will contain a unique ID field.
@@ -834,6 +897,7 @@
         private boolean mUserPresenceRequired = false;
         private byte[] mAttestationChallenge = null;
         private boolean mDevicePropertiesAttestationIncluded = false;
+        private int[] mAttestationIds = null;
         private boolean mUniqueIdIncluded = false;
         private boolean mUserAuthenticationValidWhileOnBody;
         private boolean mInvalidatedByBiometricEnrollment = true;
@@ -902,6 +966,7 @@
             mAttestationChallenge = sourceSpec.getAttestationChallenge();
             mDevicePropertiesAttestationIncluded =
                     sourceSpec.isDevicePropertiesAttestationIncluded();
+            mAttestationIds = sourceSpec.getAttestationIds();
             mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
             mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
             mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment();
@@ -1473,6 +1538,26 @@
         }
 
         /**
+         * @hide
+         * Sets which IDs to attest in the attestation certificate for the key. The acceptable
+         * values in this integer array are the enums specified in
+         * {@link android.security.keystore.AttestationUtils}
+         *
+         * @param attestationIds the array of ID types to attest to in the certificate.
+         *
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI
+         * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID
+         * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION
+         */
+        @SystemApi
+        @NonNull
+        public Builder setAttestationIds(@NonNull int[] attestationIds) {
+            mAttestationIds = attestationIds;
+            return this;
+        }
+
+        /**
          * @hide Only system apps can use this method.
          *
          * Sets whether to include a temporary unique ID field in the attestation certificate.
@@ -1638,6 +1723,7 @@
                     mUserPresenceRequired,
                     mAttestationChallenge,
                     mDevicePropertiesAttestationIncluded,
+                    mAttestationIds,
                     mUniqueIdIncluded,
                     mUserAuthenticationValidWhileOnBody,
                     mInvalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 3ebca6a..293ab05 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -100,6 +100,15 @@
 
     /**
      * Purpose of key: creating a shared ECDH secret through key agreement.
+     *
+     * <p>A key having this purpose can be combined with the elliptic curve public key of another
+     * party to establish a shared secret over an insecure channel. It should be used  as a
+     * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is
+     * available <a
+     * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh"
+     * >here</a>).
+     * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this
+     * article</a> for a more detailed explanation.
      */
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 8163472..1f2f853 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
         out.writeBoolean(mSpec.isUserPresenceRequired());
         out.writeByteArray(mSpec.getAttestationChallenge());
         out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded());
+        out.writeIntArray(mSpec.getAttestationIds());
         out.writeBoolean(mSpec.isUniqueIdIncluded());
         out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
         out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
@@ -160,6 +161,7 @@
         final boolean userPresenceRequired = in.readBoolean();
         final byte[] attestationChallenge = in.createByteArray();
         final boolean devicePropertiesAttestationIncluded = in.readBoolean();
+        final int[] attestationIds = in.createIntArray();
         final boolean uniqueIdIncluded = in.readBoolean();
         final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
         final boolean invalidatedByBiometricEnrollment = in.readBoolean();
@@ -195,6 +197,7 @@
                 userPresenceRequired,
                 attestationChallenge,
                 devicePropertiesAttestationIncluded,
+                attestationIds,
                 uniqueIdIncluded,
                 userAuthenticationValidWhileOnBody,
                 invalidatedByBiometricEnrollment,
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 5722c7b..e58b1cc 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -33,4 +33,8 @@
     static byte[] cloneIfNotNull(byte[] value) {
         return (value != null) ? value.clone() : null;
     }
+
+    static int[] cloneIfNotNull(int[] value) {
+        return (value != null) ? value.clone() : null;
+    }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 70e30d2..4d27c34 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,16 +18,20 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
 import android.os.Build;
 import android.security.KeyPairGeneratorSpec;
+import android.security.KeyStore;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
 import android.security.KeyStoreSecurityLevel;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterDefs;
 import android.security.keystore.ArrayUtils;
+import android.security.keystore.AttestationUtils;
+import android.security.keystore.DeviceIdAttestationException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeymasterUtils;
@@ -38,6 +42,8 @@
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyMetadata;
 import android.system.keystore2.ResponseCode;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 import android.util.Log;
 
 import libcore.util.EmptyArray;
@@ -478,7 +484,8 @@
                     }
                     throw p;
             }
-        } catch (UnrecoverableKeyException e) {
+        } catch (UnrecoverableKeyException | IllegalArgumentException
+                    | DeviceIdAttestationException e) {
             throw new ProviderException(
                     "Failed to construct key object from newly generated key pair.", e);
         } finally {
@@ -496,7 +503,7 @@
     }
 
     private void addAttestationParameters(@NonNull List<KeyParameter> params)
-            throws ProviderException {
+            throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
 
         if (challenge != null) {
@@ -526,15 +533,69 @@
                         Build.MODEL.getBytes(StandardCharsets.UTF_8)
                 ));
             }
-        } else {
-            if (mSpec.isDevicePropertiesAttestationIncluded()) {
-                throw new ProviderException("An attestation challenge must be provided when "
-                        + "requesting device properties attestation.");
+
+            int[] idTypes = mSpec.getAttestationIds();
+            if (idTypes == null) {
+                return;
+            }
+            final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+            for (int idType : idTypes) {
+                idTypesSet.add(idType);
+            }
+            TelephonyManager telephonyService = null;
+            if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI)
+                    || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) {
+                telephonyService =
+                    (TelephonyManager) KeyStore.getApplicationContext().getSystemService(
+                        Context.TELEPHONY_SERVICE);
+                if (telephonyService == null) {
+                    throw new DeviceIdAttestationException("Unable to access telephony service");
+                }
+            }
+            for (final Integer idType : idTypesSet) {
+                switch (idType) {
+                    case AttestationUtils.ID_TYPE_SERIAL:
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+                                Build.getSerial().getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    case AttestationUtils.ID_TYPE_IMEI: {
+                        final String imei = telephonyService.getImei(0);
+                        if (imei == null) {
+                            throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+                        }
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+                                imei.getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    }
+                    case AttestationUtils.ID_TYPE_MEID: {
+                        final String meid = telephonyService.getMeid(0);
+                        if (meid == null) {
+                            throw new DeviceIdAttestationException("Unable to retrieve MEID");
+                        }
+                        params.add(KeyStore2ParameterUtils.makeBytes(
+                                KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+                                meid.getBytes(StandardCharsets.UTF_8)
+                        ));
+                        break;
+                    }
+                    case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: {
+                        params.add(KeyStore2ParameterUtils.makeBool(
+                                KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION));
+                        break;
+                    }
+                    default:
+                        throw new IllegalArgumentException("Unknown device ID type " + idType);
+                }
             }
         }
     }
 
-    private Collection<KeyParameter> constructKeyGenerationArguments() {
+    private Collection<KeyParameter> constructKeyGenerationArguments()
+            throws DeviceIdAttestationException, IllegalArgumentException {
         List<KeyParameter> params = new ArrayList<>();
         params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits));
         params.add(KeyStore2ParameterUtils.makeEnum(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
index 0bf6965..5c91cf41 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -72,7 +72,7 @@
 
     private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
         List<ExtensionDisplayFeature> features = new ArrayList<>();
-        int displayId = activity.getDisplayId();
+        int displayId = activity.getDisplay().getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             Log.w(TAG, "This sample doesn't support display features on secondary displays");
             return features;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 1094a0e..d3700f8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -84,7 +84,7 @@
 
     private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
         List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
-        int displayId = activity.getDisplayId();
+        int displayId = activity.getDisplay().getDisplayId();
         if (displayId != DEFAULT_DISPLAY) {
             Log.w(TAG, "This sample doesn't support display features on secondary displays");
             return features;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 96e0559..0540aee 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -71,27 +71,6 @@
       "$(locations :wm_shell-sources)",
     out: ["wm_shell_protolog.json"],
 }
-
-filegroup {
-    name: "wm_shell_protolog.json",
-    srcs: ["res/raw/wm_shell_protolog.json"],
-}
-
-genrule {
-    name: "checked-wm_shell_protolog.json",
-    srcs: [
-        ":generate-wm_shell_protolog.json",
-        ":wm_shell_protolog.json",
-    ],
-    cmd: "cp $(location :generate-wm_shell_protolog.json) $(out) && " +
-      "{ ! (diff $(out) $(location :wm_shell_protolog.json) | grep -q '^<') || " +
-      "{ echo -e '\\n\\n################################################################\\n#\\n" +
-      "#  ERROR: ProtoLog viewer config is stale.  To update it, run:\\n#\\n" +
-      "#  cp $(location :generate-wm_shell_protolog.json) " +
-      "$(location :wm_shell_protolog.json)\\n#\\n" +
-      "################################################################\\n\\n' >&2 && false; } }",
-    out: ["wm_shell_protolog.json"],
-}
 // End ProtoLog
 
 java_library {
@@ -115,6 +94,9 @@
     resource_dirs: [
         "res",
     ],
+    java_resources: [
+        ":generate-wm_shell_protolog.json"
+    ],
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
new file mode 100644
index 0000000..cd31531
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<com.android.wm.shell.sizecompatui.SizeCompatRestartButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <ImageButton
+        android:id="@+id/size_compat_restart_button"
+        android:layout_width="@dimen/size_compat_button_size"
+        android:layout_height="@dimen/size_compat_button_size"
+        android:layout_gravity="center"
+        android:src="@drawable/size_compat_restart_button"
+        android:contentDescription="@string/restart_button_description"/>
+
+</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
deleted file mode 100644
index 9c3d84e..0000000
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ /dev/null
@@ -1,286 +0,0 @@
-{
-  "version": "1.0.0",
-  "messages": {
-    "-2076257741": {
-      "message": "Transition requested: %s %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1683614271": {
-      "message": "Existing task: id=%d component=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1671119352": {
-      "message": " Delegate animation for %s to %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "-1501874464": {
-      "message": "Fullscreen Task Appeared: #%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
-    },
-    "-1382704050": {
-      "message": "Display removed: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "-1362429294": {
-      "message": "%s onTaskAppeared Primary taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "-1340279385": {
-      "message": "Remove listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1325223370": {
-      "message": "Task appeared taskId=%d listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1312360667": {
-      "message": "createRootTask() displayId=%d winMode=%d listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-1308483871": {
-      "message": " try handler %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1297259344": {
-      "message": " animated by %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "-1269886472": {
-      "message": "Transition %s doesn't have explicit remote, search filters for match for %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "-1006733970": {
-      "message": "Display added: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "-1000962629": {
-      "message": "Animate bounds: from=%s to=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
-    },
-    "-880817403": {
-      "message": "Task vanished taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "-742394458": {
-      "message": "pair task1=%d task2=%d in AppPair=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
-    },
-    "-710770147": {
-      "message": "Add target: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
-    },
-    "-298656957": {
-      "message": "%s onTaskAppeared unknown taskId=%d winMode=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "-234284913": {
-      "message": "unpair taskId=%d pair=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
-    },
-    "138343607": {
-      "message": " try firstHandler %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "157713005": {
-      "message": "Task info changed taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "214412327": {
-      "message": "RemoteTransition directly requested for %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "274140888": {
-      "message": "Animate alpha: from=%d to=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
-    },
-    "325110414": {
-      "message": "Transition animations finished, notifying core %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "375908576": {
-      "message": "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "410592459": {
-      "message": "Invalid root leash (%s): %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "473543554": {
-      "message": "%s onTaskAppeared Supported",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "481673835": {
-      "message": "addListenerForTaskId taskId=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "564235578": {
-      "message": "Fullscreen Task Vanished: #%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
-    },
-    "580605218": {
-      "message": "Registering organizer",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "707170340": {
-      "message": " animated by firstHandler",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "900599280": {
-      "message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
-      "level": "ERROR",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPair.java"
-    },
-    "950299522": {
-      "message": "taskId %d isn't isn't in an app-pair.",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
-    },
-    "980952660": {
-      "message": "Task root back pressed taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "982027396": {
-      "message": "%s onTaskAppeared Secondary taskId=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
-    },
-    "990371881": {
-      "message": " Checking filter %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
-    },
-    "1070270131": {
-      "message": "onTransitionReady %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TRANSITIONS",
-      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
-    },
-    "1079041527": {
-      "message": "incrementPool size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "1184615936": {
-      "message": "Set drop target window visibility: displayId=%d visibility=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "1481772149": {
-      "message": "Current target: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
-    },
-    "1862198614": {
-      "message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
-    "1891981945": {
-      "message": "release entry.taskId=%s listener=%s size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "1990759023": {
-      "message": "addListenerForType types=%s listener=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
-    },
-    "2006473416": {
-      "message": "acquire entry.taskId=%s listener=%s size=%d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_TASK_ORG",
-      "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java"
-    },
-    "2057038970": {
-      "message": "Display changed: %d",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    }
-  },
-  "groups": {
-    "WM_SHELL_DRAG_AND_DROP": {
-      "tag": "WindowManagerShell"
-    },
-    "WM_SHELL_TASK_ORG": {
-      "tag": "WindowManagerShell"
-    },
-    "WM_SHELL_TRANSITIONS": {
-      "tag": "WindowManagerShell"
-    }
-  }
-}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 13f1fdd..2419865 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -48,4 +48,7 @@
 
     <!-- one handed background panel default alpha -->
     <item name="config_one_handed_background_alpha" format="float" type="dimen">0.5</item>
+
+    <!-- maximum animation duration for the icon when entering the starting window -->
+    <integer name="max_starting_window_intro_icon_anim_duration">1000</integer>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 034e65c..583964b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -173,4 +173,13 @@
 
     <!-- The width/height of the icon view on staring surface. -->
     <dimen name="starting_surface_icon_size">108dp</dimen>
+
+    <!-- The width/height of the size compat restart button. -->
+    <dimen name="size_compat_button_size">48dp</dimen>
+
+    <!-- The width of the brand image on staring surface. -->
+    <dimen name="starting_surface_brand_image_width">200dp</dimen>
+
+    <!-- The height of the brand image on staring surface. -->
+    <dimen name="starting_surface_brand_image_height">80dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index afe523a..6984ea45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -95,9 +95,16 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (!mLeashByTaskId.contains(taskId)) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mLeashByTaskId.get(taskId));
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
-        final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
         pw.println(innerPrefix + mLeashByTaskId.size() + " Tasks");
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
index aa82339..73fd693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
@@ -16,24 +16,16 @@
 
 package com.android.wm.shell;
 
-import android.util.Slog;
-
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.common.annotations.ExternalThread;
 
 import java.io.PrintWriter;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 
 /**
  * An entry point into the shell for dumping shell internal state and running adb commands.
  *
  * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
  */
+@ExternalThread
 public interface ShellCommandHandler {
     /**
      * Dumps the shell state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 982cc00..eaed24d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
@@ -38,24 +37,24 @@
 public final class ShellCommandHandlerImpl {
     private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
 
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     private final Optional<Pip> mPipOptional;
-    private final Optional<OneHanded> mOneHandedOptional;
-    private final Optional<HideDisplayCutout> mHideDisplayCutout;
+    private final Optional<OneHandedController> mOneHandedOptional;
+    private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<AppPairs> mAppPairsOptional;
     private final ShellExecutor mMainExecutor;
     private final HandlerImpl mImpl = new HandlerImpl();
 
     public static ShellCommandHandler create(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
                 splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
@@ -64,12 +63,12 @@
 
     private ShellCommandHandlerImpl(
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
             ShellExecutor mainExecutor) {
         mShellTaskOrganizer = shellTaskOrganizer;
         mLegacySplitScreenOptional = legacySplitScreenOptional;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 925bf4b..7376d98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -18,13 +18,12 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 
-import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -39,9 +38,9 @@
     private final DisplayImeController mDisplayImeController;
     private final DragAndDropController mDragAndDropController;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
     private final Optional<SplitScreenController> mSplitScreenOptional;
-    private final Optional<AppPairs> mAppPairsOptional;
+    private final Optional<AppPairsController> mAppPairsOptional;
     private final FullscreenTaskListener mFullscreenTaskListener;
     private final ShellExecutor mMainExecutor;
     private final Transitions mTransitions;
@@ -51,9 +50,9 @@
     public static ShellInit create(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -71,9 +70,9 @@
     private ShellInitImpl(DisplayImeController displayImeController,
             DragAndDropController dragAndDropController,
             ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
+            Optional<AppPairsController> appPairsOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Transitions transitions,
             ShellExecutor mainExecutor) {
@@ -97,7 +96,7 @@
         // Register the shell organizer
         mShellTaskOrganizer.registerOrganizer();
 
-        mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+        mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
         mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
 
         // Bind the splitscreen impl to the drag drop controller
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index a570c0a..efc55c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -25,6 +25,8 @@
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Binder;
@@ -38,13 +40,10 @@
 import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -83,6 +82,16 @@
         default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
         default void onTaskVanished(RunningTaskInfo taskInfo) {}
         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
+        /** Whether this task listener supports size compat UI. */
+        default boolean supportSizeCompatUI() {
+            // All TaskListeners should support size compat except PIP.
+            return true;
+        }
+        /** Attaches the a child window surface to the task surface. */
+        default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+            throw new IllegalStateException(
+                    "This task listener doesn't support child surface attachment.");
+        }
         default void dump(@NonNull PrintWriter pw, String prefix) {};
     }
 
@@ -108,26 +117,26 @@
      * compat.
      */
     @Nullable
-    private final SizeCompatUI mSizeCompatUI;
+    private final SizeCompatUIController mSizeCompatUI;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+                new StartingSurfaceDrawer(context, mainExecutor));
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUI sizeCompatUI) {
-        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
+            SizeCompatUIController sizeCompatUI) {
+        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+                new StartingSurfaceDrawer(context, mainExecutor));
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUI sizeCompatUI) {
+            Context context, @Nullable SizeCompatUIController sizeCompatUI,
+            StartingSurfaceDrawer startingSurfaceDrawer) {
         super(taskOrganizerController, mainExecutor);
-        // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
-        //  by a controller, that class should be create while porting
-        //  ActivityRecord#addStartingWindow to WMShell.
-        mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
         mSizeCompatUI = sizeCompatUI;
+        mStartingSurfaceDrawer = startingSurfaceDrawer;
     }
 
     @Override
@@ -254,6 +263,11 @@
     }
 
     @Override
+    public void copySplashScreenView(int taskId) {
+        mStartingSurfaceDrawer.copySplashScreenView(taskId);
+    }
+
+    @Override
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         synchronized (mLock) {
             onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
@@ -342,8 +356,8 @@
     }
 
     /**
-     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
-     * the UI accordingly.
+     * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+     * to update the UI accordingly.
      *
      * @param taskInfo the new Task info
      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
@@ -354,8 +368,10 @@
             return;
         }
 
-        // The task is vanished, notify to remove size compat UI on this Task if there is any.
-        if (taskListener == null) {
+        // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
+        // on this Task if there is any.
+        if (taskListener == null || !taskListener.supportSizeCompatUI()
+                || !taskInfo.topActivityInSizeCompat) {
             mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
                     null /* taskConfig */, null /* sizeCompatActivity*/,
                     null /* taskListener */);
@@ -363,10 +379,7 @@
         }
 
         mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
-                taskInfo.configuration.windowConfiguration.getBounds(),
-                // null if the top activity not in size compat.
-                taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
-                taskListener);
+                taskInfo.configuration, taskInfo.topActivityToken, taskListener);
     }
 
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index bb8a973..5992447 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -301,6 +301,14 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (mTaskInfo.taskId != taskId) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mTaskLeash);
+    }
+
+    @Override
     public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index a5dd79b..58ca1fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -30,6 +30,7 @@
 public class TaskViewFactoryController {
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
+    private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor) {
@@ -37,8 +38,11 @@
         mShellExecutor = shellExecutor;
     }
 
+    public TaskViewFactory asTaskViewFactory() {
+        return mImpl;
+    }
+
     /** Creates an {@link TaskView} */
-    @ShellMainThread
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
         TaskView taskView = new TaskView(context, mTaskOrganizer);
         executor.execute(() -> {
@@ -46,10 +50,6 @@
         });
     }
 
-    public TaskViewFactory getTaskViewFactory() {
-        return new TaskViewFactoryImpl();
-    }
-
     private class TaskViewFactoryImpl implements TaskViewFactory {
         @ExternalThread
         public void create(@UiContext Context context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
index abd9257..59271e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
@@ -27,6 +27,7 @@
 /**
  * The singleton wrapper to communicate between WindowManagerService and WMShell features
  * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ * TODO: Remove once PinnedStackListenerForwarder can be removed
  */
 public class WindowManagerShellWrapper {
     private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index bab5140..79f9dcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -214,6 +214,19 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (getRootTaskId() == taskId) {
+            b.setParent(mRootTaskLeash);
+        } else if (getTaskId1() == taskId) {
+            b.setParent(mTaskLeash1);
+        } else if (getTaskId2() == taskId) {
+            b.setParent(mTaskLeash2);
+        } else {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
index f5aa852..a9b1dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
@@ -35,8 +35,4 @@
     boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
     /** Unpairs any app-pair containing this task id. */
     void unpair(int taskId);
-    /** Dumps current status of app pairs. */
-    void dump(@NonNull PrintWriter pw, String prefix);
-    /** Called when the shell organizer has been registered. */
-    void onOrganizerRegistered();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index e380426..0415f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -51,18 +51,7 @@
     private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
     private final DisplayController mDisplayController;
 
-    /**
-     * Creates {@link AppPairs}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static AppPairs create(ShellTaskOrganizer organizer,
-            SyncTransactionQueue syncQueue, DisplayController displayController,
-            ShellExecutor mainExecutor) {
-        return new AppPairsController(organizer, syncQueue, displayController,
-                mainExecutor).mImpl;
-    }
-
-    AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
+    public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
                 DisplayController displayController, ShellExecutor mainExecutor) {
         mTaskOrganizer = organizer;
         mSyncQueue = syncQueue;
@@ -70,18 +59,22 @@
         mMainExecutor = mainExecutor;
     }
 
-    void onOrganizerRegistered() {
+    public AppPairs asAppPairs() {
+        return mImpl;
+    }
+
+    public void onOrganizerRegistered() {
         if (mPairsPool == null) {
             setPairsPool(new AppPairsPool(this));
         }
     }
 
     @VisibleForTesting
-    void setPairsPool(AppPairsPool pool) {
+    public void setPairsPool(AppPairsPool pool) {
         mPairsPool = pool;
     }
 
-    boolean pair(int taskId1, int taskId2) {
+    public boolean pair(int taskId1, int taskId2) {
         final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
         final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
         if (task1 == null || task2 == null) {
@@ -90,13 +83,13 @@
         return pair(task1, task2);
     }
 
-    boolean pair(ActivityManager.RunningTaskInfo task1,
+    public boolean pair(ActivityManager.RunningTaskInfo task1,
             ActivityManager.RunningTaskInfo task2) {
         return pairInner(task1, task2) != null;
     }
 
     @VisibleForTesting
-    AppPair pairInner(
+    public AppPair pairInner(
             @NonNull ActivityManager.RunningTaskInfo task1,
             @NonNull ActivityManager.RunningTaskInfo task2) {
         final AppPair pair = mPairsPool.acquire();
@@ -109,11 +102,11 @@
         return pair;
     }
 
-    void unpair(int taskId) {
+    public void unpair(int taskId) {
         unpair(taskId, true /* releaseToPool */);
     }
 
-    void unpair(int taskId, boolean releaseToPool) {
+    public void unpair(int taskId, boolean releaseToPool) {
         AppPair pair = mActiveAppPairs.get(taskId);
         if (pair == null) {
             for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
@@ -137,19 +130,19 @@
         }
     }
 
-    ShellTaskOrganizer getTaskOrganizer() {
+    public ShellTaskOrganizer getTaskOrganizer() {
         return mTaskOrganizer;
     }
 
-    SyncTransactionQueue getSyncTransactionQueue() {
+    public SyncTransactionQueue getSyncTransactionQueue() {
         return mSyncQueue;
     }
 
-    DisplayController getDisplayController() {
+    public DisplayController getDisplayController() {
         return mDisplayController;
     }
 
-    private void dump(@NonNull PrintWriter pw, String prefix) {
+    public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
         pw.println(prefix + this);
@@ -202,21 +195,5 @@
                 AppPairsController.this.unpair(taskId);
             });
         }
-
-        @Override
-        public void onOrganizerRegistered() {
-            mMainExecutor.execute(() -> {
-                AppPairsController.this.onOrganizerRegistered();
-            });
-        }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw, String prefix) {
-            try {
-                mMainExecutor.executeBlocking(() -> AppPairsController.this.dump(pw, prefix));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump AppPairsController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 40fdb97..0ee1f06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -398,6 +398,14 @@
         }
     }
 
+    @Override
+    public void setExpandedContentAlpha(float alpha) {
+        if (mExpandedView != null) {
+            mExpandedView.setAlpha(alpha);
+            mExpandedView.setTaskViewAlpha(alpha);
+        }
+    }
+
     /**
      * Set visibility of bubble in the expanded state.
      *
@@ -407,7 +415,7 @@
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
     @Override
-    public void setContentVisibility(boolean visibility) {
+    public void setTaskViewVisibility(boolean visibility) {
         if (mExpandedView != null) {
             mExpandedView.setContentVisibility(visibility);
         }
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 d73fc6d..047df5b 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
@@ -86,6 +86,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -192,9 +193,9 @@
     private boolean mIsStatusBarShade = true;
 
     /**
-     * Injected constructor.
+     * Creates an instance of the BubbleController.
      */
-    public static Bubbles create(Context context,
+    public static BubbleController create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             @Nullable IStatusBarService statusBarService,
@@ -211,14 +212,14 @@
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
+                logger, organizer, positioner, mainExecutor, mainHandler);
     }
 
     /**
      * Testing constructor.
      */
     @VisibleForTesting
-    public BubbleController(Context context,
+    protected BubbleController(Context context,
             BubbleData data,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -322,7 +323,7 @@
     }
 
     @VisibleForTesting
-    public Bubbles getImpl() {
+    public Bubbles asBubbles() {
         return mImpl;
     }
 
@@ -586,11 +587,15 @@
             // There were no bubbles saved for this used.
             return;
         }
-        for (BubbleEntry e : mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys)) {
-            if (canLaunchInActivityView(mContext, e)) {
-                updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
-            }
-        }
+        mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+            mMainExecutor.execute(() -> {
+                for (BubbleEntry e : entries) {
+                    if (canLaunchInActivityView(mContext, e)) {
+                        updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+                    }
+                }
+            });
+        });
         // Finally, remove the entries for this user now that bubbles are restored.
         mSavedBubbleKeysPerUser.remove(mCurrentUserId);
     }
@@ -856,21 +861,24 @@
         }
     }
 
-    private void onRankingUpdated(RankingMap rankingMap) {
+    private void onRankingUpdated(RankingMap rankingMap,
+            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
         if (mTmpRanking == null) {
             mTmpRanking = new NotificationListenerService.Ranking();
         }
         String[] orderedKeys = rankingMap.getOrderedKeys();
         for (int i = 0; i < orderedKeys.length; i++) {
             String key = orderedKeys[i];
-            BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(key);
+            Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
+            BubbleEntry entry = entryData.first;
+            boolean shouldBubbleUp = entryData.second;
             rankingMap.getRanking(key, mTmpRanking);
             boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
             if (isActiveBubble && !mTmpRanking.canBubble()) {
                 // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
                 // This means that the app or channel's ability to bubble has been revoked.
                 mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
-            } else if (isActiveBubble && !mSysuiProxy.shouldBubbleUp(key)) {
+            } else if (isActiveBubble && !shouldBubbleUp) {
                 // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
                 // This happens when DND is enabled and configured to hide bubbles. Dismissing with
                 // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
@@ -919,17 +927,20 @@
     private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
         Objects.requireNonNull(b);
         b.setIsBubble(isBubble);
-        final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(b.getKey());
-        if (entry != null) {
-            // Updating the entry to be a bubble will trigger our normal update flow
-            setIsBubble(entry, isBubble, b.shouldAutoExpand());
-        } else if (isBubble) {
-            // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
-            // stack ourselves
-            Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
-            inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
-                    !bubble.shouldAutoExpand() /* showInShade */);
-        }
+        mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> {
+            mMainExecutor.execute(() -> {
+                if (entry != null) {
+                    // Updating the entry to be a bubble will trigger our normal update flow
+                    setIsBubble(entry, isBubble, b.shouldAutoExpand());
+                } else if (isBubble) {
+                    // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
+                    // stack ourselves
+                    Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
+                    inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
+                            !bubble.shouldAutoExpand() /* showInShade */);
+                }
+            });
+        });
     }
 
     @SuppressWarnings("FieldCanBeLocal")
@@ -992,14 +1003,17 @@
                     }
 
                 }
-                final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
-                if (entry != null) {
-                    final String groupKey = entry.getStatusBarNotification().getGroupKey();
-                    if (getBubblesInGroup(groupKey).isEmpty()) {
-                        // Time to potentially remove the summary
-                        mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
-                    }
-                }
+                mSysuiProxy.getPendingOrActiveEntry(bubble.getKey(), (entry) -> {
+                    mMainExecutor.execute(() -> {
+                        if (entry != null) {
+                            final String groupKey = entry.getStatusBarNotification().getGroupKey();
+                            if (getBubblesInGroup(groupKey).isEmpty()) {
+                                // Time to potentially remove the summary
+                                mSysuiProxy.notifyMaybeCancelSummary(bubble.getKey());
+                            }
+                        }
+                    });
+                });
             }
             mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
 
@@ -1121,23 +1135,6 @@
         mStackView.updateContentDescription();
     }
 
-    /**
-     * The task id of the expanded view, if the stack is expanded and not occluded by the
-     * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
-     */
-    private int getExpandedTaskId() {
-        if (mStackView == null) {
-            return INVALID_TASK_ID;
-        }
-        final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
-        if (expandedViewProvider != null && isStackExpanded()
-                && !mStackView.isExpansionAnimating()
-                && !mSysuiProxy.isNotificationShadeExpand()) {
-            return expandedViewProvider.getTaskId();
-        }
-        return INVALID_TASK_ID;
-    }
-
     @VisibleForTesting
     public BubbleStackView getStackView() {
         return mStackView;
@@ -1343,9 +1340,10 @@
         }
 
         @Override
-        public void onRankingUpdated(RankingMap rankingMap) {
+        public void onRankingUpdated(RankingMap rankingMap,
+                HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
             mMainExecutor.execute(() -> {
-                BubbleController.this.onRankingUpdated(rankingMap);
+                BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey);
             });
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 29458ef..2f31acd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -85,6 +85,19 @@
     private boolean mImeVisible;
     private boolean mNeedsNewHeight;
 
+    /**
+     * Whether we want the TaskView's content to be visible (alpha = 1f). If
+     * {@link #mIsAlphaAnimating} is true, this may not reflect the TaskView's actual alpha value
+     * until the animation ends.
+     */
+    private boolean mIsContentVisible = false;
+
+    /**
+     * Whether we're animating the TaskView's alpha value. If so, we will hold off on applying alpha
+     * changes from {@link #setContentVisibility} until the animation ends.
+     */
+    private boolean mIsAlphaAnimating = false;
+
     private int mMinHeight;
     private int mOverflowHeight;
     private int mSettingsIconHeight;
@@ -150,6 +163,7 @@
                         // Apply flags to make behaviour match documentLaunchMode=always.
                         fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                         fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                        fillInIntent.putExtra(Intent.EXTRA_IS_BUBBLED, true);
                         if (mBubble != null) {
                             mBubble.setIntentActive();
                         }
@@ -465,6 +479,29 @@
     }
 
     /**
+     * Whether we are currently animating the TaskView's alpha value. If this is set to true, calls
+     * to {@link #setContentVisibility} will not be applied until this is set to false again.
+     */
+    void setAlphaAnimating(boolean animating) {
+        mIsAlphaAnimating = animating;
+
+        // If we're done animating, apply the correct
+        if (!animating) {
+            setContentVisibility(mIsContentVisible);
+        }
+    }
+
+    /**
+     * Sets the alpha of the underlying TaskView, since changing the expanded view's alpha does not
+     * affect the TaskView since it uses a Surface.
+     */
+    void setTaskViewAlpha(float alpha) {
+        if (mTaskView != null) {
+            mTaskView.setAlpha(alpha);
+        }
+    }
+
+    /**
      * Set visibility of contents in the expanded state.
      *
      * @param visibility {@code true} if the contents should be visible on the screen.
@@ -477,16 +514,19 @@
             Log.d(TAG, "setContentVisibility: visibility=" + visibility
                     + " bubble=" + getBubbleKey());
         }
+        mIsContentVisible = visibility;
+
         final float alpha = visibility ? 1f : 0f;
 
         mPointerView.setAlpha(alpha);
-        if (mTaskView != null) {
+        if (mTaskView != null && !mIsAlphaAnimating) {
             mTaskView.setAlpha(alpha);
         }
     }
 
+
     @Nullable
-    View getTaskView() {
+    TaskView getTaskView() {
         return mTaskView;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 16cd3cf..51d63cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -167,8 +167,12 @@
         return dotPath
     }
 
-    override fun setContentVisibility(visible: Boolean) {
-        expandedView?.setContentVisibility(visible)
+    override fun setExpandedContentAlpha(alpha: Float) {
+        expandedView?.alpha = alpha
+    }
+
+    override fun setTaskViewVisibility(visible: Boolean) {
+        // Overflow does not have a TaskView.
     }
 
     override fun getIconView(): BadgedImageView? {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d54be0e..a3edc20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -43,7 +43,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Bundle;
-import android.os.Handler;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Choreographer;
@@ -123,6 +122,10 @@
     @VisibleForTesting
     static final int FLYOUT_HIDE_AFTER = 5000;
 
+    private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
+
+    private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+
     /**
      * How long to wait to animate the stack temporarily invisible after a drag/flyout hide
      * animation ends, if we are in fact temporarily invisible.
@@ -142,7 +145,7 @@
 
     private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
             new PhysicsAnimator.SpringConfig(
-                    SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+                    SpringForce.STIFFNESS_VERY_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
 
     /**
      * Handler to use for all delayed animations - this way, we can easily cancel them before
@@ -211,6 +214,9 @@
     /** Container for the animating-out SurfaceView. */
     private FrameLayout mAnimatingOutSurfaceContainer;
 
+    /** Animator for animating the alpha value of the animating out SurfaceView. */
+    private final ValueAnimator mAnimatingOutSurfaceAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
     /**
      * Buffer containing a screenshot of the animating-out bubble. This is drawn into the
      * SurfaceView during animations.
@@ -261,6 +267,12 @@
     /** Whether we're in the middle of dragging the stack around by touch. */
     private boolean mIsDraggingStack = false;
 
+    /** Whether the expanded view has been hidden, because we are dragging out a bubble. */
+    private boolean mExpandedViewHidden = false;
+
+    /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
+    private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+
     /**
      * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
      * touches from other pointer indices.
@@ -614,6 +626,12 @@
             // Show the dismiss target, if we haven't already.
             mDismissView.show();
 
+            if (mIsExpanded && mExpandedBubble != null && v.equals(mExpandedBubble.getIconView())) {
+                // Hide the expanded view if we're dragging out the expanded bubble, and we haven't
+                // already hidden it.
+                hideExpandedViewIfNeeded();
+            }
+
             // First, see if the magnetized object consumes the event - if so, we shouldn't move the
             // bubble since it's stuck to the target.
             if (!passEventToMagnetizedObject(ev)) {
@@ -645,6 +663,9 @@
             if (!passEventToMagnetizedObject(ev)) {
                 if (mBubbleData.isExpanded()) {
                     mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+
+                    // Re-show the expanded view if we hid it.
+                    showExpandedViewIfNeeded();
                 } else {
                     // Fling the stack to the edge, and save whether or not it's going to end up on
                     // the left side of the screen.
@@ -937,6 +958,46 @@
         animate()
                 .setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
                 .setDuration(FADE_IN_DURATION);
+
+        mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+        mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+        mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                    // We need to be Z ordered on top in order for alpha animations to work.
+                    mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
+                    mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                    mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+                    mExpandedBubble.getExpandedView().setAlphaAnimating(false);
+                }
+            }
+        });
+        mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
+            if (mExpandedBubble != null) {
+                mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+            }
+        });
+
+        mAnimatingOutSurfaceAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
+        mAnimatingOutSurfaceAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+        mAnimatingOutSurfaceAlphaAnimator.addUpdateListener(valueAnimator -> {
+            if (!mExpandedViewHidden) {
+                mAnimatingOutSurfaceView.setAlpha((float) valueAnimator.getAnimatedValue());
+            }
+        });
+        mAnimatingOutSurfaceAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                releaseAnimatingOutBubbleBuffer();
+            }
+        });
     }
 
     /**
@@ -1539,7 +1600,8 @@
 
         // If we're expanded, screenshot the currently expanded bubble (before expanding the newly
         // selected bubble) so we can animate it out.
-        if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+        if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null
+                && !mExpandedViewHidden) {
             if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
                 // Before screenshotting, have the real ActivityView show on top of other surfaces
                 // so that the screenshot doesn't flicker on top of it.
@@ -1575,7 +1637,7 @@
             mExpandedViewContainer.setAlpha(0.0f);
             mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
                 if (previouslySelected != null) {
-                    previouslySelected.setContentVisibility(false);
+                    previouslySelected.setTaskViewVisibility(false);
                 }
 
                 updateExpandedBubble();
@@ -1656,6 +1718,58 @@
         requestUpdate();
     }
 
+    /** Animate the expanded view hidden. This is done while we're dragging out a bubble. */
+    private void hideExpandedViewIfNeeded() {
+        if (mExpandedViewHidden
+                || mExpandedBubble == null
+                || mExpandedBubble.getExpandedView() == null) {
+            return;
+        }
+
+        mExpandedViewHidden = true;
+
+        // Scale down.
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .addUpdateListener((target, values) ->
+                        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+                .start();
+
+        // Animate alpha from 1f to 0f.
+        mExpandedViewAlphaAnimator.reverse();
+    }
+
+    /**
+     * Animate the expanded view visible again. This is done when we're done dragging out a bubble.
+     */
+    private void showExpandedViewIfNeeded() {
+        if (!mExpandedViewHidden) {
+            return;
+        }
+
+        mExpandedViewHidden = false;
+
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
+                        mScaleOutSpringConfig)
+                .addUpdateListener((target, values) ->
+                        mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix))
+                .start();
+
+        mExpandedViewAlphaAnimator.start();
+    }
+
     private void animateExpansion() {
         cancelDelayedExpandCollapseSwitchAnimations();
         final boolean showVertically = mPositioner.showBubblesVertically();
@@ -1714,7 +1828,7 @@
         // Should not happen since we lay out before expanding, but just in case...
         if (getWidth() > 0) {
             startDelay = (long)
-                    (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
+                    (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 1.2f
                             + (distanceAnimated / getWidth()) * 30);
         }
 
@@ -1728,20 +1842,29 @@
                 pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
             }
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     pivotX, pivotY);
         } else {
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
-                    bubbleWillBeAt + mBubbleSize / 2f, getExpandedViewY());
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    bubbleWillBeAt + mBubbleSize / 2f,
+                    getExpandedViewY());
         }
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
 
-        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+        if (mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.setExpandedContentAlpha(0f);
+
+            // We'll be starting the alpha animation after a slight delay, so set this flag early
+            // here.
+            mExpandedBubble.getExpandedView().setAlphaAnimating(true);
         }
 
         mDelayedAnimationExecutor.executeDelayed(() -> {
+            mExpandedViewAlphaAnimator.start();
+
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
                     .spring(AnimatableScaleMatrix.SCALE_X,
@@ -1796,22 +1919,15 @@
         // since we're about to animate collapsed.
         mExpandedAnimationController.notifyPreparingToCollapse();
 
-        final long startDelay =
-                (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
-        mDelayedAnimationExecutor.executeDelayed(() -> {
-            mExpandedAnimationController.collapseBackToStack(
-                    mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                    /* collapseTo */,
-                    () -> mBubbleContainer.setActiveController(mStackAnimationController));
-        }, startDelay);
+        mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> mBubbleContainer.setActiveController(mStackAnimationController));
 
         if (mTaskbarScrim.getVisibility() == VISIBLE) {
             mTaskbarScrim.animate().alpha(0f).start();
         }
 
-        // We want to visually collapse into this bubble during the animation.
-        final View expandingFromBubble = mExpandedBubble.getIconView();
-
         int index;
         if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
             index = mBubbleData.getBubbles().size();
@@ -1840,31 +1956,25 @@
                     getExpandedViewY());
         }
 
+        mExpandedViewAlphaAnimator.reverse();
+
+        // When the animation completes, we should no longer be showing the content.
+        if (mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.getExpandedView().setContentVisibility(false);
+        }
+
         PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
         PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
-                .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
-                .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_X,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
+                .spring(AnimatableScaleMatrix.SCALE_Y,
+                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
+                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
+                        mScaleOutSpringConfig)
                 .addUpdateListener((target, values) -> {
-                    if (expandingFromBubble != null) {
-                        // Follow the bubble as it translates!
-                        if (showVertically) {
-                            mExpandedViewContainerMatrix.postTranslate(
-                                    0f, expandingFromBubble.getTranslationY()
-                                            - expandingFromBubbleAt);
-                        } else {
-                            mExpandedViewContainerMatrix.postTranslate(
-                                    expandingFromBubble.getTranslationX()
-                                            - expandingFromBubbleAt, 0f);
-                        }
-                    }
-
                     mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
-                    // Hide early so we don't have a tiny little expanded view still visible at the
-                    // end of the scale animation.
-                    if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
-                        mExpandedViewContainer.setVisibility(View.INVISIBLE);
-                    }
                 })
                 .withEndActions(() -> {
                     final BubbleViewProvider previouslySelected = mExpandedBubble;
@@ -1882,7 +1992,7 @@
                     updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
                     afterExpandedViewAnimation();
                     if (previouslySelected != null) {
-                        previouslySelected.setContentVisibility(false);
+                        previouslySelected.setTaskViewVisibility(false);
                     }
 
                     if (mPositioner.showingInTaskbar()) {
@@ -1903,22 +2013,21 @@
         // The surface contains a screenshot of the animating out bubble, so we just need to animate
         // it out (and then release the GraphicBuffer).
         PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
-        PhysicsAnimator animator = PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
-                .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
-                .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
-                .withEndActions(this::releaseAnimatingOutBubbleBuffer);
+
+        mAnimatingOutSurfaceAlphaAnimator.reverse();
+        mExpandedViewAlphaAnimator.start();
 
         if (mPositioner.showBubblesVertically()) {
             float translationX = mStackAnimationController.isStackOnLeftSide()
                     ? mAnimatingOutSurfaceContainer.getTranslationX() + mBubbleSize * 2
                     : mAnimatingOutSurfaceContainer.getTranslationX();
-            animator.spring(DynamicAnimation.TRANSLATION_X,
-                    translationX,
-                    mTranslateSpringConfig)
+            PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+                    .spring(DynamicAnimation.TRANSLATION_X, translationX, mTranslateSpringConfig)
                     .start();
         } else {
-            animator.spring(DynamicAnimation.TRANSLATION_Y,
-                    mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
+            PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
+                    .spring(DynamicAnimation.TRANSLATION_Y,
+                            mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize,
                     mTranslateSpringConfig)
                     .start();
         }
@@ -1947,7 +2056,8 @@
                     pivotX, pivotY);
         } else {
             mExpandedViewContainerMatrix.setScale(
-                    0f, 0f,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+                    1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                     expandingFromBubbleDestination + mBubbleSize / 2f,
                     getExpandedViewY());
         }
@@ -1972,10 +2082,7 @@
                         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
                     })
                     .withEndActions(() -> {
-                        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                            mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
-                        }
-
+                        mExpandedViewHidden = false;
                         mIsBubbleSwitchAnimating = false;
                     })
                     .start();
@@ -2489,6 +2596,7 @@
                 && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             bev.setContentVisibility(false);
+            bev.setAlphaAnimating(!mIsExpansionAnimating);
             mExpandedViewContainerMatrix.setScaleX(0f);
             mExpandedViewContainerMatrix.setScaleY(0f);
             mExpandedViewContainerMatrix.setTranslate(0f, 0f);
@@ -2586,7 +2694,14 @@
                     mAnimatingOutBubbleBuffer.getHardwareBuffer(),
                     mAnimatingOutBubbleBuffer.getColorSpace());
 
-            mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
+            mAnimatingOutSurfaceView.setAlpha(1f);
+            mExpandedViewContainer.setVisibility(View.GONE);
+
+            mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+                post(() -> {
+                    onComplete.accept(true);
+                });
+            });
         });
     }
 
@@ -2618,7 +2733,9 @@
             }
         }
         mExpandedViewContainer.setPadding(leftPadding, 0, rightPadding, 0);
-        mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+        if (mIsExpansionAnimating) {
+            mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
+        }
         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
             mExpandedViewContainer.setTranslationY(getExpandedViewY());
             mExpandedViewContainer.setTranslationX(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index ec900be..da4259c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,7 +29,16 @@
 public interface BubbleViewProvider {
     @Nullable BubbleExpandedView getExpandedView();
 
-    void setContentVisibility(boolean visible);
+    /**
+     * Sets the alpha of the expanded view content. This will be applied to both the expanded view
+     * container itself (the manage button, etc.) as well as the TaskView within it.
+     */
+    void setExpandedContentAlpha(float alpha);
+
+    /**
+     * Sets whether the contents of the bubble's TaskView should be visible.
+     */
+    void setTaskViewVisibility(boolean visible);
 
     @Nullable View getIconView();
 
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 6a1026b..8e061e9 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
@@ -26,6 +26,7 @@
 import android.os.Looper;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.view.View;
 
 import androidx.annotation.IntDef;
@@ -37,6 +38,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -182,8 +184,11 @@
      * permissions on the notification channel or the global setting.
      *
      * @param rankingMap the updated ranking map from NotificationListenerService
+     * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
+     *                       bubble up
      */
-    void onRankingUpdated(RankingMap rankingMap);
+    void onRankingUpdated(RankingMap rankingMap,
+            HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey);
 
     /**
      * Called when the status bar has become visible or invisible (either permanently or
@@ -243,14 +248,10 @@
 
     /** Callback to tell SysUi components execute some methods. */
     interface SysuiProxy {
-        @Nullable
-        BubbleEntry getPendingOrActiveEntry(String key);
+        void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
 
-        List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys);
-
-        boolean isNotificationShadeExpand();
-
-        boolean shouldBubbleUp(String key);
+        void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+                Consumer<List<BubbleEntry>> callback);
 
         void setNotificationInterruption(String key);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 728f60d..87f0c25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -39,6 +39,7 @@
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
 
@@ -55,6 +56,7 @@
     private final ParentContainerCallbacks mParentContainerCallbacks;
     private Context mContext;
     private SurfaceControlViewHost mViewHost;
+    private SurfaceControl mLeash;
     private boolean mResizingSplits;
     private final String mWindowName;
 
@@ -88,7 +90,15 @@
 
     @Override
     protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
-        mParentContainerCallbacks.attachToParentSurface(b);
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName(TAG)
+                .setHidden(false)
+                .setCallsite("SplitWindowManager#attachToParentSurface");
+        mParentContainerCallbacks.attachToParentSurface(builder);
+        mLeash = builder.build();
+        b.setParent(mLeash);
     }
 
     /** Inflates {@link DividerView} on to the root surface. */
@@ -118,9 +128,15 @@
      * hierarchy.
      */
     void release() {
-        if (mViewHost == null) return;
-        mViewHost.release();
-        mViewHost = null;
+        if (mViewHost != null){
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            new SurfaceControl.Transaction().remove(mLeash).apply();
+            mLeash = null;
+        }
     }
 
     void setResizingSplits(boolean resizing) {
@@ -139,6 +155,6 @@
      */
     @Nullable
     SurfaceControl getSurfaceControl() {
-        return mViewHost == null ? null : getSurfaceControl(mViewHost.getWindowToken());
+        return mLeash;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
index 3a2f0da..60123ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
@@ -31,12 +31,6 @@
 public interface HideDisplayCutout {
     /**
      * Notifies {@link Configuration} changed.
-     * @param newConfig
      */
     void onConfigurationChanged(Configuration newConfig);
-
-    /**
-     * Dumps hide display cutout status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 12b8b87..23f76ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -44,20 +44,12 @@
     @VisibleForTesting
     boolean mEnabled;
 
-    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
-            ShellExecutor mainExecutor) {
-        mContext = context;
-        mOrganizer = organizer;
-        mMainExecutor = mainExecutor;
-        updateStatus();
-    }
-
     /**
      * Creates {@link HideDisplayCutoutController}, returns {@code null} if the feature is not
      * supported.
      */
     @Nullable
-    public static HideDisplayCutout create(
+    public static HideDisplayCutoutController create(
             Context context, DisplayController displayController, ShellExecutor mainExecutor) {
         // The SystemProperty is set for devices that support this feature and is used to control
         // whether to create the HideDisplayCutout instance.
@@ -68,7 +60,19 @@
 
         HideDisplayCutoutOrganizer organizer =
                 new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
-        return new HideDisplayCutoutController(context, organizer, mainExecutor).mImpl;
+        return new HideDisplayCutoutController(context, organizer, mainExecutor);
+    }
+
+    HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mOrganizer = organizer;
+        mMainExecutor = mainExecutor;
+        updateStatus();
+    }
+
+    public HideDisplayCutout asHideDisplayCutout() {
+        return mImpl;
     }
 
     @VisibleForTesting
@@ -94,7 +98,7 @@
         updateStatus();
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String prefix = "  ";
         pw.print(TAG);
         pw.println(" states: ");
@@ -111,14 +115,5 @@
                 HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            try {
-                mMainExecutor.executeBlocking(() -> HideDisplayCutoutController.this.dump(pw));
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Failed to dump HideDisplayCutoutController in 2s");
-            }
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index bca6deb..d25bef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -115,21 +115,6 @@
     private volatile boolean mAdjustedForIme = false;
     private boolean mHomeStackResizable = false;
 
-    /**
-     * Creates {@link SplitScreen}, returns {@code null} if the feature is not supported.
-     */
-    @Nullable
-    public static LegacySplitScreen create(Context context,
-            DisplayController displayController, SystemWindows systemWindows,
-            DisplayImeController imeController, TransactionPool transactionPool,
-            ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
-            TaskStackListenerImpl taskStackListener, Transitions transitions,
-            ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
-        return new LegacySplitScreenController(context, displayController, systemWindows,
-                imeController, transactionPool, shellTaskOrganizer, syncQueue, taskStackListener,
-                transitions, mainExecutor, sfVsyncAnimationHandler).mImpl;
-    }
-
     public LegacySplitScreenController(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController imeController, TransactionPool transactionPool,
@@ -228,8 +213,12 @@
                     }
                 });
     }
+    
+    public LegacySplitScreen asLegacySplitScreen() {
+        return mImpl;
+    }
 
-    void onSplitScreenSupported() {
+    public void onSplitScreenSupported() {
         // Set starting tile bounds based on middle target
         final WindowContainerTransaction tct = new WindowContainerTransaction();
         int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -237,7 +226,7 @@
         mTaskOrganizer.applyTransaction(tct);
     }
 
-    private void onKeyguardVisibilityChanged(boolean showing) {
+    public void onKeyguardVisibilityChanged(boolean showing) {
         if (!isSplitActive() || mView == null) {
             return;
         }
@@ -293,19 +282,19 @@
         }
     }
 
-    boolean isMinimized() {
+    public boolean isMinimized() {
         return mMinimized;
     }
 
-    boolean isHomeStackResizable() {
+    public boolean isHomeStackResizable() {
         return mHomeStackResizable;
     }
 
-    DividerView getDividerView() {
+    public DividerView getDividerView() {
         return mView;
     }
 
-    boolean isDividerVisible() {
+    public boolean isDividerVisible() {
         return mView != null && mView.getVisibility() == View.VISIBLE;
     }
 
@@ -314,13 +303,13 @@
      * isDividerVisible because the divider is only visible once *everything* is in split mode
      * while this only cares if some things are (eg. while entering/exiting as well).
      */
-    private boolean isSplitActive() {
+    public boolean isSplitActive() {
         return mSplits.mPrimary != null && mSplits.mSecondary != null
                 && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
                 || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
     }
 
-    private void addDivider(Configuration configuration) {
+    public void addDivider(Configuration configuration) {
         Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
         mView = (DividerView)
                 LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
@@ -338,14 +327,14 @@
         mWindowManager.add(mView, width, height, mContext.getDisplayId());
     }
 
-    private void removeDivider() {
+    public void removeDivider() {
         if (mView != null) {
             mView.onDividerRemoved();
         }
         mWindowManager.remove();
     }
 
-    private void update(Configuration configuration) {
+    public void update(Configuration configuration) {
         final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
 
         removeDivider();
@@ -358,11 +347,11 @@
         mView.setHidden(isDividerHidden);
     }
 
-    void onTaskVanished() {
+    public void onTaskVanished() {
         removeDivider();
     }
 
-    private void updateVisibility(final boolean visible) {
+    public void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
@@ -390,7 +379,7 @@
         }
     }
 
-    private void setMinimized(final boolean minimized) {
+    public void setMinimized(final boolean minimized) {
         if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
         mMainExecutor.execute(() -> {
             if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
@@ -401,7 +390,7 @@
         });
     }
 
-    private void setHomeMinimized(final boolean minimized) {
+    public void setHomeMinimized(final boolean minimized) {
         if (DEBUG) {
             Slog.d(TAG, "setHomeMinimized  min:" + mMinimized + "->" + minimized + " hrsz:"
                     + mHomeStackResizable + " split:" + isDividerVisible());
@@ -441,7 +430,7 @@
         }
     }
 
-    void setAdjustedForIme(boolean adjustedForIme) {
+    public void setAdjustedForIme(boolean adjustedForIme) {
         if (mAdjustedForIme == adjustedForIme) {
             return;
         }
@@ -449,30 +438,30 @@
         updateTouchable();
     }
 
-    private void updateTouchable() {
+    public void updateTouchable() {
         mWindowManager.setTouchable(!mAdjustedForIme);
     }
 
-    private void onUndockingTask() {
+    public void onUndockingTask() {
         if (mView != null) {
             mView.onUndockingTask();
         }
     }
 
-    private void onAppTransitionFinished() {
+    public void onAppTransitionFinished() {
         if (mView == null) {
             return;
         }
         mForcedResizableController.onAppTransitionFinished();
     }
 
-    private void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.print("  mVisible="); pw.println(mVisible);
         pw.print("  mMinimized="); pw.println(mMinimized);
         pw.print("  mAdjustedForIme="); pw.println(mAdjustedForIme);
     }
 
-    long getAnimDuration() {
+    public long getAnimDuration() {
         float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
                 Settings.Global.TRANSITION_ANIMATION_SCALE,
                 mContext.getResources().getFloat(
@@ -482,14 +471,14 @@
         return (long) (transitionDuration * transitionScale);
     }
 
-    void registerInSplitScreenListener(Consumer<Boolean> listener) {
+    public void registerInSplitScreenListener(Consumer<Boolean> listener) {
         listener.accept(isDividerVisible());
         synchronized (mDockedStackExistsListeners) {
             mDockedStackExistsListeners.add(new WeakReference<>(listener));
         }
     }
 
-    void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
+    public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
         synchronized (mDockedStackExistsListeners) {
             for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
                 if (mDockedStackExistsListeners.get(i) == listener) {
@@ -499,13 +488,13 @@
         }
     }
 
-    private void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
+    public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.add(new WeakReference<>(listener));
         }
     }
 
-    private boolean splitPrimaryTask() {
+    public boolean splitPrimaryTask() {
         try {
             if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
                     || isSplitActive()) {
@@ -538,12 +527,12 @@
                 topRunningTask.taskId, true /* onTop */);
     }
 
-    private void dismissSplitToPrimaryTask() {
+    public void dismissSplitToPrimaryTask() {
         startDismissSplit(true /* toPrimaryTask */);
     }
 
     /** Notifies the bounds of split screen changed. */
-    void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+    public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
         synchronized (mBoundsChangedListeners) {
             mBoundsChangedListeners.removeIf(wf -> {
                 BiConsumer<Rect, Rect> l = wf.get();
@@ -553,19 +542,19 @@
         }
     }
 
-    void startEnterSplit() {
+    public void startEnterSplit() {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         // Set resizable directly here because applyEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
-    void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
+    public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
         // Set resizable directly here because buildEnterSplit already resizes home stack.
         mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, mSplitLayout);
     }
 
-    void finishEnterSplitTransition(boolean minimized) {
+    public void finishEnterSplitTransition(boolean minimized) {
         update(mDisplayController.getDisplayContext(
                 mContext.getDisplayId()).getResources().getConfiguration());
         if (minimized) {
@@ -575,11 +564,11 @@
         }
     }
 
-    void startDismissSplit(boolean toPrimaryTask) {
+    public void startDismissSplit(boolean toPrimaryTask) {
         startDismissSplit(toPrimaryTask, false /* snapped */);
     }
 
-    void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
+    public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mSplits.getSplitTransitions().dismissSplit(
                     mSplits, mSplitLayout, !toPrimaryTask, snapped);
@@ -589,7 +578,7 @@
         }
     }
 
-    void onDismissSplit() {
+    public void onDismissSplit() {
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
@@ -599,7 +588,7 @@
         mImePositionProcessor.reset();
     }
 
-    void ensureMinimizedSplit() {
+    public void ensureMinimizedSplit() {
         setHomeMinimized(true /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode yet, so enter now.
@@ -610,7 +599,7 @@
         }
     }
 
-    void ensureNormalSplit() {
+    public void ensureNormalSplit() {
         setHomeMinimized(false /* minimized */);
         if (mView != null && !isDividerVisible()) {
             // Wasn't in split-mode, so enter now.
@@ -621,15 +610,15 @@
         }
     }
 
-    LegacySplitDisplayLayout getSplitLayout() {
+    public LegacySplitDisplayLayout getSplitLayout() {
         return mSplitLayout;
     }
 
-    WindowManagerProxy getWmProxy() {
+    public WindowManagerProxy getWmProxy() {
         return mWindowManagerProxy;
     }
 
-    WindowContainerToken getSecondaryRoot() {
+    public WindowContainerToken getSecondaryRoot() {
         if (mSplits == null || mSplits.mSecondary == null) {
             return null;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
index 5a493c2..0552601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
@@ -323,6 +323,14 @@
     }
 
     @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (!mLeashByTaskId.contains(taskId)) {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+        b.setParent(mLeashByTaskId.get(taskId));
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         final String childPrefix = innerPrefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 94c6f01..c8f8987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -39,7 +39,6 @@
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -116,7 +115,7 @@
     void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
         WindowContainerTransaction t = new WindowContainerTransaction();
         splitLayout.resizeSplits(position, t);
-        new WindowOrganizer().applyTransaction(t);
+        applySyncTransaction(t);
     }
 
     boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index e958648..11c11f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -69,9 +69,4 @@
      * 3 button navigation mode only
      */
     void registerGestureCallback(OneHandedGestureEventCallback callback);
-
-    /**
-     * Dump one handed status.
-     */
-    void dump(@NonNull PrintWriter pw);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index eaa704f..5a3c38b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -134,10 +134,10 @@
 
 
     /**
-     * Creates {@link OneHanded}, returns {@code null} if the feature is not supported.
+     * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
      */
     @Nullable
-    public static OneHanded create(
+    public static OneHandedController create(
             Context context, DisplayController displayController,
             TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger,
             ShellExecutor mainExecutor, Handler mainHandler) {
@@ -166,7 +166,7 @@
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
                 gestureHandler, timeoutHandler, oneHandedUiEventsLogger, overlayManager,
-                taskStackListener, mainExecutor, mainHandler).mImpl;
+                taskStackListener, mainExecutor, mainHandler);
     }
 
     @VisibleForTesting
@@ -228,6 +228,10 @@
                 mAccessibilityStateChangeListener);
     }
 
+    public OneHanded asOneHanded() {
+        return mImpl;
+    }
+
     /**
      * Set one handed enabled or disabled when user update settings
      */
@@ -468,7 +472,7 @@
         }
     }
 
-    private void dump(@NonNull PrintWriter pw) {
+    public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG + "states: ");
         pw.print(innerPrefix + "mOffSetFraction=");
@@ -561,12 +565,5 @@
                 OneHandedController.this.registerGestureCallback(callback);
             });
         }
-
-        @Override
-        public void dump(@NonNull PrintWriter pw) {
-            mMainExecutor.execute(() -> {
-                OneHandedController.this.dump(pw);
-            });
-        }
     }
 }
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 ad6f435..71331df 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
@@ -66,7 +66,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.transition.Transitions;
 
@@ -131,7 +131,7 @@
     private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreenController> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -207,7 +207,7 @@
             @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             @NonNull PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreenController> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -584,6 +584,12 @@
     }
 
     @Override
+    public boolean supportSizeCompatUI() {
+        // PIP doesn't support size compat.
+        return false;
+    }
+
+    @Override
     public void onFixedRotationStarted(int displayId, int newRotation) {
         mNextRotation = newRotation;
         mWaitForFixedRotation = true;
@@ -1047,7 +1053,8 @@
     }
 
     /**
-     * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
+     * Sync with {@link LegacySplitScreenController} on destination bounds if PiP is going to split
+     * screen.
      *
      * @param destinationBoundsOut contain the updated destination bounds if applicable
      * @return {@code true} if destinationBounds is altered for split screen
@@ -1057,7 +1064,7 @@
             return false;
         }
 
-        LegacySplitScreen legacySplitScreen = mSplitScreenOptional.get();
+        LegacySplitScreenController legacySplitScreen = mSplitScreenOptional.get();
         if (!legacySplitScreen.isDividerVisible()) {
             // fail early if system is not in split screen mode
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index ae53005..725f87d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -375,17 +375,29 @@
     }
 
     /**
-     * Hides the menu activity.
+     * Hides the menu view.
      */
     public void hideMenu() {
+        hideMenu(true /* animate */, true /* resize */);
+    }
+
+    /**
+     * Hides the menu view.
+     *
+     * @param animate whether to animate the menu fadeout
+     * @param resize whether or not to resize the PiP with the state change
+     */
+    public void hideMenu(boolean animate, boolean resize) {
         final boolean isMenuVisible = isMenuVisible();
         if (DEBUG) {
             Log.d(TAG, "hideMenu() state=" + mMenuState
                     + " isMenuVisible=" + isMenuVisible
+                    + " animate=" + animate
+                    + " resize=" + resize
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         if (isMenuVisible) {
-            mPipMenuView.hideMenu();
+            mPipMenuView.hideMenu(animate, resize);
         }
     }
 
@@ -404,15 +416,6 @@
     }
 
     /**
-     * Preemptively mark the menu as invisible, used when we are directly manipulating the pinned
-     * stack and don't want to trigger a resize which can animate the stack in a conflicting way
-     * (ie. when manually expanding or dismissing).
-     */
-    public void hideMenuWithoutResize() {
-        onMenuStateChanged(MENU_STATE_NONE, false /* resize */, null /* callback */);
-    }
-
-    /**
      * Sets the menu actions to the actions provided by the current PiP menu.
      */
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 962c467..1cf3a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -89,7 +89,6 @@
     private static final boolean ENABLE_RESIZE_HANDLE = false;
 
     private int mMenuState;
-    private boolean mResize = true;
     private boolean mAllowMenuTimeout = true;
     private boolean mAllowTouches = true;
 
@@ -329,16 +328,21 @@
         hideMenu(null);
     }
 
+    void hideMenu(boolean animate, boolean resize) {
+        hideMenu(null, true /* notifyMenuVisibility */, animate, resize);
+    }
+
     void hideMenu(Runnable animationEndCallback) {
-        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */);
+        hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */,
+                true /* resize */);
     }
 
     private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
-            boolean animate) {
+            boolean animate, boolean resize) {
         if (mMenuState != MENU_STATE_NONE) {
             cancelDelayedHide();
             if (notifyMenuVisibility) {
-                notifyMenuStateChange(MENU_STATE_NONE, mResize, null);
+                notifyMenuStateChange(MENU_STATE_NONE, resize, null);
             }
             mMenuContainerAnimator = new AnimatorSet();
             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -469,7 +473,8 @@
     private void expandPip() {
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
-        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */);
+        hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */,
+                true /* resize */);
     }
 
     private void dismissPip() {
@@ -479,7 +484,8 @@
         final boolean animate = mMenuState != MENU_STATE_CLOSE;
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
-        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate);
+        hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate,
+                true /* resize */);
     }
 
     private void showSettings() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index b19dcae..4bcd76f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -325,7 +325,7 @@
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
-        mMenuController.hideMenuWithoutResize();
+        mMenuController.hideMenu(false /* animate */, false /* resize */);
         mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
     }
 
@@ -338,7 +338,7 @@
             Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
-        mMenuController.hideMenuWithoutResize();
+        mMenuController.hideMenu(true /* animate*/, false /* resize */);
         mPipTaskOrganizer.removePip();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 53571ff..78ee186 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -106,7 +106,7 @@
     // For pinch-resize
     private boolean mThresholdCrossed0;
     private boolean mThresholdCrossed1;
-    private boolean mUsingPinchToZoom = false;
+    private boolean mUsingPinchToZoom = true;
     private float mAngle = 0;
     int mFirstIndex = -1;
     int mSecondIndex = -1;
@@ -139,7 +139,7 @@
         mEnablePinchResize = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_PINCH_RESIZE,
-                /* defaultValue = */ false);
+                /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                 mMainExecutor,
                 new DeviceConfig.OnPropertiesChangedListener() {
@@ -147,7 +147,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) {
                             mEnablePinchResize = properties.getBoolean(
-                                    PIP_PINCH_RESIZE, /* defaultValue = */ false);
+                                    PIP_PINCH_RESIZE, /* defaultValue = */ true);
                         }
                     }
                 });
@@ -516,8 +516,8 @@
                     }
                     if (mThresholdCrossed) {
                         if (mPhonePipMenuController.isMenuVisible()) {
-                            mPhonePipMenuController.hideMenuWithoutResize();
-                            mPhonePipMenuController.hideMenu();
+                            mPhonePipMenuController.hideMenu(false /* animate */,
+                                    false /* resize */);
                         }
                         final Rect currentPipBounds = mPipBoundsState.getBounds();
                         mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
@@ -558,8 +558,8 @@
                         || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
                     mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
                 }
-                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
-                        mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
+                final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds);
+                mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
                         PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index e69c6f2..f3875ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -63,8 +63,9 @@
  * the PIP.
  */
 public class PipTouchHandler {
+    @VisibleForTesting static final float MINIMUM_SIZE_PERCENT = 0.4f;
+
     private static final String TAG = "PipTouchHandler";
-    private static final float MINIMUM_SIZE_PERCENT = 0.4f;
     private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
 
     // Allow PIP to resize to a slightly bigger state upon touch
@@ -876,6 +877,9 @@
                             < mPipBoundsState.getMaxSize().x
                             && mPipBoundsState.getBounds().height()
                             < mPipBoundsState.getMaxSize().y;
+                    if (mMenuController.isMenuVisible()) {
+                        mMenuController.hideMenu(false /* animate */, false /* resize */);
+                    }
                     if (toExpand) {
                         mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                         animateToMaximizedState(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 4f4e7da..2b0a0cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,9 +26,9 @@
 public enum ShellProtoLogGroup implements IProtoLogGroup {
     // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
     // with those in the framework ProtoLogGroup
-    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+    WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 66ecf45..552ebde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -28,6 +28,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 
 import org.json.JSONException;
@@ -109,10 +110,10 @@
         return sServiceInstance;
     }
 
-    public int startTextLogging(Context context, String[] groups, PrintWriter pw) {
-        try {
-            mViewerConfig.loadViewerConfig(
-                    context.getResources().openRawResource(R.raw.wm_shell_protolog));
+    public int startTextLogging(String[] groups, PrintWriter pw) {
+        try (InputStream is =
+                     getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
+            mViewerConfig.loadViewerConfig(is);
             return setLogging(true /* setTextLogging */, true, pw, groups);
         } catch (IOException e) {
             Log.i(TAG, "Unable to load log definitions: IOException while reading "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
index e47e1ac..9094d7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -16,101 +16,80 @@
 
 package com.android.wm.shell.sizecompatui;
 
-import android.app.ActivityClient;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.RippleDrawable;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.Gravity;
+import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.Button;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 
 /** Button to restart the size compat activity. */
-class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener,
         View.OnLongClickListener {
-    private static final String TAG = "SizeCompatRestartButton";
 
-    final WindowManager.LayoutParams mWinParams;
-    final boolean mShouldShowHint;
-    final int mDisplayId;
-    final int mPopupOffsetX;
-    final int mPopupOffsetY;
+    private SizeCompatUILayout mLayout;
+    private ImageButton mRestartButton;
+    @VisibleForTesting
+    PopupWindow mShowingHint;
+    private WindowManager.LayoutParams mWinParams;
 
-    private IBinder mLastActivityToken;
-    private PopupWindow mShowingHint;
-
-    SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+    public SizeCompatRestartButton(@NonNull Context context) {
         super(context);
-        mDisplayId = displayId;
-        mShouldShowHint = !hasShownHint;
-        final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
-        setImageDrawable(drawable);
-        setContentDescription(context.getString(R.string.restart_button_description));
+    }
 
-        final int drawableW = drawable.getIntrinsicWidth();
-        final int drawableH = drawable.getIntrinsicHeight();
-        mPopupOffsetX = drawableW / 2;
-        mPopupOffsetY = drawableH * 2;
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
 
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    void inject(SizeCompatUILayout layout) {
+        mLayout = layout;
+        mWinParams = layout.getWindowLayoutParams();
+    }
+
+    void remove() {
+        dismissHint();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mRestartButton = findViewById(R.id.size_compat_restart_button);
         final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
         final GradientDrawable mask = new GradientDrawable();
         mask.setShape(GradientDrawable.OVAL);
         mask.setColor(color);
-        setBackground(new RippleDrawable(color, null /* content */, mask));
-        setOnClickListener(this);
-        setOnLongClickListener(this);
-
-        mWinParams = new WindowManager.LayoutParams();
-        mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
-        mWinParams.width = drawableW * 2;
-        mWinParams.height = drawableH * 2;
-        mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-        mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-        mWinParams.format = PixelFormat.TRANSLUCENT;
-        mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
-                + context.getDisplayId());
-    }
-
-    void updateLastTargetActivity(IBinder activityToken) {
-        mLastActivityToken = activityToken;
-    }
-
-    /** @return {@code false} if the target display is invalid. */
-    boolean show() {
-        try {
-            getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
-        } catch (WindowManager.InvalidDisplayException e) {
-            // The target display may have been removed when the callback has just arrived.
-            Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
-            return false;
-        }
-        return true;
-    }
-
-    void remove() {
-        if (mShowingHint != null) {
-            mShowingHint.dismiss();
-        }
-        getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+        mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
+        mRestartButton.setOnClickListener(this);
+        mRestartButton.setOnLongClickListener(this);
     }
 
     @Override
     public void onClick(View v) {
-        ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+        mLayout.onRestartButtonClicked();
     }
 
     @Override
@@ -122,20 +101,26 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mShouldShowHint) {
+        if (mLayout.mShouldShowHint) {
+            mLayout.mShouldShowHint = false;
             showHint();
         }
     }
 
     @Override
+    public void setVisibility(@Visibility int visibility) {
+        if (visibility == View.GONE && mShowingHint != null) {
+            // Also dismiss the popup.
+            dismissHint();
+        }
+        super.setVisibility(visibility);
+    }
+
+    @Override
     public void setLayoutDirection(int layoutDirection) {
-        final int gravity = getGravity(layoutDirection);
+        final int gravity = SizeCompatUILayout.getGravity(layoutDirection);
         if (mWinParams.gravity != gravity) {
             mWinParams.gravity = gravity;
-            if (mShowingHint != null) {
-                mShowingHint.dismiss();
-                showHint();
-            }
             getContext().getSystemService(WindowManager.class).updateViewLayout(this,
                     mWinParams);
         }
@@ -147,8 +132,10 @@
             return;
         }
 
+        // TODO: popup is not attached to the button surface. Need to handle this differently for
+        // non-fullscreen task.
         final View popupView = LayoutInflater.from(getContext()).inflate(
-                R.layout.size_compat_mode_hint, null /* root */);
+                R.layout.size_compat_mode_hint, null);
         final PopupWindow popupWindow = new PopupWindow(popupView,
                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
         popupWindow.setWindowLayoutType(mWinParams.type);
@@ -161,12 +148,15 @@
         final Button gotItButton = popupView.findViewById(R.id.got_it);
         gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
                 null /* content */, null /* mask */));
-        gotItButton.setOnClickListener(view -> popupWindow.dismiss());
-        popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+        gotItButton.setOnClickListener(view -> dismissHint());
+        popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX,
+                mLayout.mPopupOffsetY);
     }
 
-    private static int getGravity(int layoutDirection) {
-        return Gravity.BOTTOM
-                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    void dismissHint() {
+        if (mShowingHint != null) {
+            mShowingHint.dismiss();
+            mShowingHint = null;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
deleted file mode 100644
index 11f22ed..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ /dev/null
@@ -1,45 +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.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage size compat mode UI.
- */
-@ExternalThread
-public interface SizeCompatUI {
-    /**
-     * Called when the Task info changed. Creates and updates the restart button if there is an
-     * activity in size compat, or removes the restart button if there is no size compat activity.
-     *
-     * @param displayId display the task and activity are in.
-     * @param taskId task the activity is in.
-     * @param taskBounds task bounds to place the restart button in.
-     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
-     *                           top activity in this Task is not in size compat.
-     * @param taskListener listener to handle the Task Surface placement.
-     */
-    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
-            @Nullable ShellTaskOrganizer.TaskListener taskListener);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 286c3b6..a3880f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -18,144 +18,166 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
-import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
- * Shows a restart-activity button on Task when the foreground activity is in size compatibility
- * mode.
+ * Controls to show/update restart-activity buttons on Tasks based on whether the foreground
+ * activities are in size compatibility mode.
  */
 public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
         DisplayImeController.ImePositionProcessor {
-    private static final String TAG = "SizeCompatUI";
+    private static final String TAG = "SizeCompatUIController";
+
+    /** Whether the IME is shown on display id. */
+    private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
 
     /** The showing buttons by task id. */
-    private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+    private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
+
     /** Avoid creating display context frequently for non-default display. */
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
-    @VisibleForTesting
-    final SizeCompatUI mImpl = new SizeCompatUIImpl();
     private final Context mContext;
-    private final ShellExecutor mMainExecutor;
     private final DisplayController mDisplayController;
     private final DisplayImeController mImeController;
+    private final SyncTransactionQueue mSyncQueue;
 
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
 
-    /** Creates the {@link SizeCompatUIController}. */
-    public static SizeCompatUI create(Context context,
+    public SizeCompatUIController(Context context,
             DisplayController displayController,
             DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
-                .mImpl;
-    }
-
-    @VisibleForTesting
-    SizeCompatUIController(Context context,
-            DisplayController displayController,
-            DisplayImeController imeController,
-            ShellExecutor mainExecutor) {
+            SyncTransactionQueue syncQueue) {
         mContext = context;
-        mMainExecutor = mainExecutor;
         mDisplayController = displayController;
         mImeController = imeController;
+        mSyncQueue = syncQueue;
         mDisplayController.addDisplayWindowListener(this);
         mImeController.addPositionProcessor(this);
     }
 
-    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-            @Nullable IBinder sizeCompatActivity,
+    /**
+     * Called when the Task info changed. Creates and updates the restart button if there is an
+     * activity in size compat, or removes the restart button if there is no size compat activity.
+     *
+     * @param displayId display the task and activity are in.
+     * @param taskId task the activity is in.
+     * @param taskConfig task config to place the restart button with.
+     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
+     *                           top activity in this Task is not in size compat.
+     * @param taskListener listener to handle the Task Surface placement.
+     */
+    public void onSizeCompatInfoChanged(int displayId, int taskId,
+            @Nullable Configuration taskConfig, @Nullable IBinder sizeCompatActivity,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-        // TODO Draw button on Task surface
-        if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+        if (taskConfig == null || sizeCompatActivity == null || taskListener == null) {
             // Null token means the current foreground activity is not in size compatibility mode.
-            removeRestartButton(taskId);
+            removeLayout(taskId);
+        } else if (mActiveLayouts.contains(taskId)) {
+            // Button already exists, update the button layout.
+            updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener);
         } else {
-            updateRestartButton(displayId, taskId, sizeCompatActivity);
+            // Create a new restart button.
+            createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener);
         }
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
         mDisplayContextCache.remove(displayId);
-        for (int i = 0; i < mActiveButtons.size(); i++) {
-            final int taskId = mActiveButtons.keyAt(i);
-            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-            if (button != null && button.mDisplayId == displayId) {
-                removeRestartButton(taskId);
-            }
+
+        // Remove all buttons on the removed display.
+        final List<Integer> toRemoveTaskIds = new ArrayList<>();
+        forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
+        for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
+            removeLayout(toRemoveTaskIds.get(i));
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
-        final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
-        for (int i = 0; i < mActiveButtons.size(); i++) {
-            final int taskId = mActiveButtons.keyAt(i);
-            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-            if (button == null || button.mDisplayId != displayId) {
-                continue;
-            }
-
-            // Hide the button when input method is showing.
-            if (button.getVisibility() != newVisibility) {
-                button.setVisibility(newVisibility);
-            }
-        }
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
+        forAllLayoutsOnDisplay(displayId, layout -> layout.updateDisplayLayout(displayLayout));
     }
 
-    private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
-        SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
-        if (restartButton != null) {
-            restartButton.updateLastTargetActivity(activityToken);
-            return;
+    @Override
+    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+        if (isShowing) {
+            mDisplaysWithIme.add(displayId);
+        } else {
+            mDisplaysWithIme.remove(displayId);
         }
 
+        // Hide the button when input method is showing.
+        forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
+    }
+
+    private boolean isImeShowingOnDisplay(int displayId) {
+        return mDisplaysWithIme.contains(displayId);
+    }
+
+    private void createLayout(int displayId, int taskId, Configuration taskConfig,
+            IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener) {
         final Context context = getOrCreateDisplayContext(displayId);
         if (context == null) {
-            Log.i(TAG, "Cannot get context for display " + displayId);
+            Log.e(TAG, "Cannot get context for display " + displayId);
             return;
         }
 
-        restartButton = createRestartButton(context, displayId);
-        restartButton.updateLastTargetActivity(activityToken);
-        if (restartButton.show()) {
-            mActiveButtons.append(taskId, restartButton);
-        } else {
-            onDisplayRemoved(displayId);
-        }
+        final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
+                activityToken, taskListener);
+        mActiveLayouts.put(taskId, layout);
+        layout.createSizeCompatButton(isImeShowingOnDisplay(displayId));
     }
 
     @VisibleForTesting
-    SizeCompatRestartButton createRestartButton(Context context, int displayId) {
-        final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+    SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+            Configuration taskConfig, IBinder activityToken,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, context, taskConfig,
+                taskId, activityToken, taskListener, mDisplayController.getDisplayLayout(displayId),
                 mHasShownHint);
         // Only show hint for the first time.
         mHasShownHint = true;
-        return button;
+        return layout;
     }
 
-    private void removeRestartButton(int taskId) {
-        final SizeCompatRestartButton button = mActiveButtons.get(taskId);
-        if (button != null) {
-            button.remove();
-            mActiveButtons.remove(taskId);
+    private void updateLayout(int taskId, Configuration taskConfig,
+            IBinder sizeCompatActivity,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        if (layout == null) {
+            return;
+        }
+        layout.updateSizeCompatInfo(taskConfig, sizeCompatActivity, taskListener,
+                isImeShowingOnDisplay(layout.getDisplayId()));
+    }
+
+    private void removeLayout(int taskId) {
+        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        if (layout != null) {
+            layout.release();
+            mActiveLayouts.remove(taskId);
         }
     }
 
@@ -178,14 +200,13 @@
         return context;
     }
 
-    private class SizeCompatUIImpl implements SizeCompatUI {
-        @Override
-        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
-                @Nullable IBinder sizeCompatActivity,
-                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
-            mMainExecutor.execute(() ->
-                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
-                            taskBounds, sizeCompatActivity, taskListener));
+    private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+        for (int i = 0; i < mActiveLayouts.size(); i++) {
+            final int taskId = mActiveLayouts.keyAt(i);
+            final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+            if (layout != null && layout.getDisplayId() == displayId) {
+                callback.accept(layout);
+            }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
new file mode 100644
index 0000000..5924b53
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -0,0 +1,235 @@
+/*
+ * 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.wm.shell.sizecompatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.app.ActivityClient;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Records and handles layout of size compat UI on a task with size compat activity. Helps to
+ * calculate proper bounds when configuration or button position changes.
+ */
+class SizeCompatUILayout {
+    private static final String TAG = "SizeCompatUILayout";
+
+    private final SyncTransactionQueue mSyncQueue;
+    private Context mContext;
+    private Configuration mTaskConfig;
+    private final int mDisplayId;
+    private final int mTaskId;
+    private IBinder mActivityToken;
+    private ShellTaskOrganizer.TaskListener mTaskListener;
+    private DisplayLayout mDisplayLayout;
+    @VisibleForTesting
+    final SizeCompatUIWindowManager mWindowManager;
+
+    @VisibleForTesting
+    @Nullable
+    SizeCompatRestartButton mButton;
+    final int mButtonSize;
+    final int mPopupOffsetX;
+    final int mPopupOffsetY;
+    boolean mShouldShowHint;
+
+    SizeCompatUILayout(SyncTransactionQueue syncQueue, Context context, Configuration taskConfig,
+            int taskId, IBinder activityToken, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout, boolean hasShownHint) {
+        mSyncQueue = syncQueue;
+        mContext = context.createConfigurationContext(taskConfig);
+        mTaskConfig = taskConfig;
+        mDisplayId = mContext.getDisplayId();
+        mTaskId = taskId;
+        mActivityToken = activityToken;
+        mTaskListener = taskListener;
+        mDisplayLayout = displayLayout;
+        mShouldShowHint = !hasShownHint;
+        mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
+
+        mButtonSize =
+                mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
+        mPopupOffsetX = mButtonSize / 4;
+        mPopupOffsetY = mButtonSize;
+    }
+
+    /** Creates the button window. */
+    void createSizeCompatButton(boolean isImeShowing) {
+        if (isImeShowing || mButton != null) {
+            // When ime is showing, wait until ime is dismiss to create UI.
+            return;
+        }
+        mButton = mWindowManager.createSizeCompatUI();
+        updateSurfacePosition();
+    }
+
+    /** Releases the button window. */
+    void release() {
+        mButton.remove();
+        mButton = null;
+        mWindowManager.release();
+    }
+
+    /** Called when size compat info changed. */
+    void updateSizeCompatInfo(Configuration taskConfig, IBinder activityToken,
+            ShellTaskOrganizer.TaskListener taskListener, boolean isImeShowing) {
+        final Configuration prevTaskConfig = mTaskConfig;
+        final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+        mTaskConfig = taskConfig;
+        mActivityToken = activityToken;
+        mTaskListener = taskListener;
+
+        // Update configuration.
+        mContext = mContext.createConfigurationContext(taskConfig);
+        mWindowManager.setConfiguration(taskConfig);
+
+        if (mButton == null || prevTaskListener != taskListener) {
+            // TaskListener changed, recreate the button for new surface parent.
+            release();
+            createSizeCompatButton(isImeShowing);
+            return;
+        }
+
+        if (!taskConfig.windowConfiguration.getBounds()
+                .equals(prevTaskConfig.windowConfiguration.getBounds())) {
+            // Reposition the button surface.
+            updateSurfacePosition();
+        }
+
+        if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
+            // Update layout for RTL.
+            mButton.setLayoutDirection(taskConfig.getLayoutDirection());
+            updateSurfacePosition();
+        }
+    }
+
+    /** Called when display layout changed. */
+    void updateDisplayLayout(DisplayLayout displayLayout) {
+        if (displayLayout == mDisplayLayout) {
+            return;
+        }
+
+        final Rect prevStableBounds = new Rect();
+        final Rect curStableBounds = new Rect();
+        mDisplayLayout.getStableBounds(prevStableBounds);
+        displayLayout.getStableBounds(curStableBounds);
+        mDisplayLayout = displayLayout;
+        if (!prevStableBounds.equals(curStableBounds)) {
+            // Stable bounds changed, update button surface position.
+            updateSurfacePosition();
+        }
+    }
+
+    /** Called when IME visibility changed. */
+    void updateImeVisibility(boolean isImeShowing) {
+        if (mButton == null) {
+            // Button may not be created because ime is previous showing.
+            createSizeCompatButton(isImeShowing);
+            return;
+        }
+
+        final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
+        if (mButton.getVisibility() != newVisibility) {
+            mButton.setVisibility(newVisibility);
+        }
+    }
+
+    /** Gets the layout params for restart button. */
+    WindowManager.LayoutParams getWindowLayoutParams() {
+        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+                mButtonSize, mButtonSize,
+                TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        winParams.gravity = getGravity(getLayoutDirection());
+        winParams.token = new Binder();
+        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId());
+        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        return winParams;
+    }
+
+    /** Called when it is ready to be placed button surface button. */
+    void attachToParentSurface(SurfaceControl.Builder b) {
+        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+    }
+
+    /** Called when the restart button is clicked. */
+    void onRestartButtonClicked() {
+        ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken);
+    }
+
+    @VisibleForTesting
+    void updateSurfacePosition() {
+        if (mButton == null || mWindowManager.getSurfaceControl() == null) {
+            return;
+        }
+        // The hint popup won't be at the correct position.
+        mButton.dismissHint();
+
+        // Use stable bounds to prevent the button from overlapping with system bars.
+        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+        final Rect stableBounds = new Rect();
+        mDisplayLayout.getStableBounds(stableBounds);
+        stableBounds.intersect(taskBounds);
+
+        // Position of the button in the container coordinate.
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? stableBounds.left - taskBounds.left
+                : stableBounds.right - taskBounds.left - mButtonSize;
+        final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;
+
+        mSyncQueue.runInSync(t ->
+                t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY));
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    private int getLayoutDirection() {
+        return mContext.getResources().getConfiguration().getLayoutDirection();
+    }
+
+    static int getGravity(int layoutDirection) {
+        return Gravity.BOTTOM
+                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
new file mode 100644
index 0000000..a7ad982
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.sizecompatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.WindowlessWindowManager;
+
+import com.android.wm.shell.R;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton}.
+ */
+class SizeCompatUIWindowManager extends WindowlessWindowManager {
+
+    private Context mContext;
+    private final SizeCompatUILayout mLayout;
+
+    @Nullable
+    private SurfaceControlViewHost mViewHost;
+    @Nullable
+    private SurfaceControl mLeash;
+
+    SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) {
+        super(config, null /* rootSurface */, null /* hostInputToken */);
+        mContext = context;
+        mLayout = layout;
+    }
+
+    @Override
+    public void setConfiguration(Configuration configuration) {
+        super.setConfiguration(configuration);
+        mContext = mContext.createConfigurationContext(configuration);
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName("SizeCompatUILeash")
+                .setHidden(false)
+                .setCallsite("SizeCompatUIWindowManager#attachToParentSurface");
+        mLayout.attachToParentSurface(builder);
+        mLeash = builder.build();
+        b.setParent(mLeash);
+    }
+
+    /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
+    SizeCompatRestartButton createSizeCompatUI() {
+        if (mViewHost == null) {
+            mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+        }
+
+        final SizeCompatRestartButton button = (SizeCompatRestartButton)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+        button.inject(mLayout);
+        mViewHost.setView(button, mLayout.getWindowLayoutParams());
+        return button;
+    }
+
+    /** Releases the surface control and tears down the view hierarchy. */
+    void release() {
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            new SurfaceControl.Transaction().remove(mLeash).apply();
+            mLeash = null;
+        }
+    }
+
+    /**
+     * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null}
+     * if not feasible.
+     */
+    @Nullable
+    SurfaceControl getSurfaceControl() {
+        return mLeash;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 177646b..7ca5693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -106,6 +106,8 @@
 
     /** Removes the split-screen stages. */
     void exitSplitScreen();
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide);
     /** Gets the stage bounds. */
     void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index bbad36d..b0167af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -126,6 +126,10 @@
         mStageCoordinator.exitSplitScreen();
     }
 
+    public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+        mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+    }
+
     public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
         mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
     }
@@ -292,6 +296,13 @@
         }
 
         @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide);
+            });
+        }
+
+        @Override
         public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
             try {
                 mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 176852b..e44c820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -79,6 +79,7 @@
     private DisplayAreaInfo mDisplayAreaInfo;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private boolean mExitSplitScreenOnHide = true;
 
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -113,7 +114,7 @@
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
             @SplitScreen.StagePosition int sideStagePosition) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mSideStagePosition = sideStagePosition;
+        setSideStagePosition(sideStagePosition);
         mMainStage.activate(getMainStageBounds(), wct);
         mSideStage.addTask(task, getSideStageBounds(), wct);
         mTaskOrganizer.applyTransaction(wct);
@@ -144,14 +145,18 @@
     }
 
     void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
+        if (mSideStagePosition == sideStagePosition) return;
+
         mSideStagePosition = sideStagePosition;
         if (mSideStageListener.mVisible) {
             onStageVisibilityChanged(mSideStageListener);
         }
+
+        sendOnStagePositionChanged();
     }
 
     void setSideStageVisibility(boolean visible) {
-        if (!mSideStageListener.mVisible == visible) return;
+        if (mSideStageListener.mVisible == visible) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mSideStage.setVisibility(visible, wct);
@@ -162,6 +167,10 @@
         exitSplitScreen(null /* childrenToTop */);
     }
 
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+        mExitSplitScreenOnHide = exitSplitScreenOnHide;
+    }
+
     private void exitSplitScreen(StageTaskListener childrenToTop) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
@@ -202,6 +211,14 @@
         mListeners.remove(listener);
     }
 
+    private void sendOnStagePositionChanged() {
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            final SplitScreen.SplitScreenListener l = mListeners.get(i);
+            l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+            l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+        }
+    }
+
     private void onStageChildTaskStatusChanged(
             StageListenerImpl stageListener, int taskId, boolean present) {
 
@@ -251,7 +268,7 @@
             }
         }
 
-        if (!mainStageVisible && !sideStageVisible) {
+        if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) {
             // Exit split-screen if both stage are not visible.
             // TODO: This is only a temporary request from UX and is likely to be removed soon...
             exitSplitScreen();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6532993..10c742b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -130,6 +130,17 @@
         }
     }
 
+    @Override
+    public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+        if (mRootTaskInfo.taskId == taskId) {
+            b.setParent(mRootLeash);
+        } else if (mChildrenLeashes.contains(taskId)) {
+            b.setParent(mChildrenLeashes.get(taskId));
+        } else {
+            throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+        }
+    }
+
     void setBounds(Rect bounds, WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, bounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f3f2fc3..45d5515 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -31,23 +31,21 @@
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
 import android.util.Slog;
-import android.view.Gravity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
+import android.view.Window;
+import android.window.SplashScreenView;
 
 import com.android.internal.R;
 import com.android.internal.graphics.palette.Palette;
 import com.android.internal.graphics.palette.Quantizer;
 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
-import com.android.internal.policy.PhoneWindow;
 
 import java.util.List;
 
 /**
  * Util class to create the view for a splash screen content.
+ * @hide
  */
-class SplashscreenContentDrawer {
+public class SplashscreenContentDrawer {
     private static final String TAG = StartingSurfaceDrawer.TAG;
     private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_SPLASH_SCREEN;
 
@@ -58,15 +56,24 @@
     // also 108*108 pixels, then do not enlarge this icon if only need to show foreground icon.
     private static final float ENLARGE_FOREGROUND_ICON_THRESHOLD = (72f * 72f) / (108f * 108f);
     private final Context mContext;
-    private int mIconSize;
+    private final int mMaxIconAnimationDuration;
 
-    SplashscreenContentDrawer(Context context) {
+    private int mIconSize;
+    private int mBrandingImageWidth;
+    private int mBrandingImageHeight;
+
+    SplashscreenContentDrawer(Context context, int maxIconAnimationDuration) {
         mContext = context;
+        mMaxIconAnimationDuration = maxIconAnimationDuration;
     }
 
     private void updateDensity() {
         mIconSize = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.starting_surface_icon_size);
+        mBrandingImageWidth = mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.starting_surface_brand_image_width);
+        mBrandingImageHeight = mContext.getResources().getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.starting_surface_brand_image_height);
     }
 
     private int getSystemBGColor() {
@@ -83,48 +90,92 @@
         return new ColorDrawable(getSystemBGColor());
     }
 
-    View makeSplashScreenContentView(PhoneWindow win, Context context, int iconRes,
+    SplashScreenView makeSplashScreenContentView(Window win, Context context, int iconRes,
             int splashscreenContentResId) {
         updateDensity();
-        win.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
         // splash screen content will be deprecated after S.
-        final View ssc = makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
+        final SplashScreenView ssc =
+                makeSplashscreenContentDrawable(win, context, splashscreenContentResId);
         if (ssc != null) {
             return ssc;
         }
 
-        final TypedArray typedArray = context.obtainStyledAttributes(
-                com.android.internal.R.styleable.Window);
-        final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
-        typedArray.recycle();
+        final SplashScreenWindowAttrs attrs =
+                SplashScreenWindowAttrs.createWindowAttrs(context);
+        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
         final Drawable themeBGDrawable;
-        if (resId == 0) {
+
+        if (attrs.mWindowBgColor != 0) {
+            themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
+        } else if (attrs.mWindowBgResId != 0) {
+            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+        } else {
             Slog.w(TAG, "Window background not exist!");
             themeBGDrawable = createDefaultBackgroundDrawable();
-        } else {
-            themeBGDrawable = context.getDrawable(resId);
         }
-        final Drawable iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
-                : context.getPackageManager().getDefaultActivityIcon();
+        final int animationDuration;
+        final Drawable iconDrawable;
+        if (attrs.mReplaceIcon != null) {
+            iconDrawable = attrs.mReplaceIcon;
+            animationDuration = Math.max(0,
+                    Math.min(attrs.mAnimationDuration, mMaxIconAnimationDuration));
+        } else {
+            iconDrawable = iconRes != 0 ? context.getDrawable(iconRes)
+                    : context.getPackageManager().getDefaultActivityIcon();
+            animationDuration = 0;
+        }
         // TODO (b/173975965) Tracking the performance on improved splash screen.
-        final StartingWindowViewBuilder builder = new StartingWindowViewBuilder();
         return builder
-                .setPhoneWindow(win)
+                .setWindow(win)
                 .setContext(context)
                 .setThemeDrawable(themeBGDrawable)
-                .setIconDrawable(iconDrawable).build();
+                .setIconDrawable(iconDrawable)
+                .setIconAnimationDuration(animationDuration)
+                .setBrandingDrawable(attrs.mBrandingImage).build();
+    }
+
+    private static class SplashScreenWindowAttrs {
+        private int mWindowBgResId = 0;
+        private int mWindowBgColor = Color.TRANSPARENT;
+        private Drawable mReplaceIcon = null;
+        private Drawable mBrandingImage = null;
+        private int mAnimationDuration = 0;
+
+        static SplashScreenWindowAttrs createWindowAttrs(Context context) {
+            final SplashScreenWindowAttrs attrs = new SplashScreenWindowAttrs();
+            final TypedArray typedArray = context.obtainStyledAttributes(
+                    com.android.internal.R.styleable.Window);
+            attrs.mWindowBgResId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+            attrs.mWindowBgColor = typedArray.getColor(
+                    R.styleable.Window_windowSplashScreenBackground, Color.TRANSPARENT);
+            attrs.mReplaceIcon = typedArray.getDrawable(
+                    R.styleable.Window_windowSplashScreenAnimatedIcon);
+            attrs.mAnimationDuration = typedArray.getInt(
+                    R.styleable.Window_windowSplashScreenAnimationDuration, 0);
+            attrs.mBrandingImage = typedArray.getDrawable(
+                    R.styleable.Window_windowSplashScreenBrandingImage);
+            typedArray.recycle();
+            if (DEBUG) {
+                Slog.d(TAG, "window attributes color: "
+                        + Integer.toHexString(attrs.mWindowBgColor)
+                        + " icon " + attrs.mReplaceIcon + " duration " + attrs.mAnimationDuration
+                        + " brandImage " + attrs.mBrandingImage);
+            }
+            return attrs;
+        }
     }
 
     private class StartingWindowViewBuilder {
-        // materials
         private Drawable mThemeBGDrawable;
         private Drawable mIconDrawable;
-        private PhoneWindow mPhoneWindow;
+        private Window mWindow;
+        private int mIconAnimationDuration;
         private Context mContext;
+        private Drawable mBrandingDrawable;
 
         // result
         private boolean mBuildComplete = false;
-        private View mCachedResult;
+        private SplashScreenView mCachedResult;
         private int mThemeColor;
         private Drawable mFinalIconDrawable;
         private float mScale = 1f;
@@ -141,8 +192,20 @@
             return this;
         }
 
-        StartingWindowViewBuilder setPhoneWindow(PhoneWindow window) {
-            mPhoneWindow = window;
+        StartingWindowViewBuilder setWindow(Window window) {
+            mWindow = window;
+            mBuildComplete = false;
+            return this;
+        }
+
+        StartingWindowViewBuilder setIconAnimationDuration(int iconAnimationDuration) {
+            mIconAnimationDuration = iconAnimationDuration;
+            mBuildComplete = false;
+            return this;
+        }
+
+        StartingWindowViewBuilder setBrandingDrawable(Drawable branding) {
+            mBrandingDrawable = branding;
             mBuildComplete = false;
             return this;
         }
@@ -153,11 +216,11 @@
             return this;
         }
 
-        View build() {
+        SplashScreenView build() {
             if (mBuildComplete) {
                 return mCachedResult;
             }
-            if (mPhoneWindow == null || mContext == null) {
+            if (mWindow == null || mContext == null) {
                 Slog.e(TAG, "Unable to create StartingWindowView, lack of materials!");
                 return null;
             }
@@ -173,7 +236,7 @@
                 mFinalIconDrawable = mIconDrawable;
             }
             final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
-            mCachedResult = fillViewWithIcon(mPhoneWindow, mContext, iconSize, mFinalIconDrawable);
+            mCachedResult = fillViewWithIcon(mWindow, mContext, iconSize, mFinalIconDrawable);
             mBuildComplete = true;
             return mCachedResult;
         }
@@ -249,25 +312,26 @@
             return true;
         }
 
-        private View fillViewWithIcon(PhoneWindow win, Context context,
+        private SplashScreenView fillViewWithIcon(Window win, Context context,
                 int iconSize, Drawable iconDrawable) {
-            final StartingSurfaceWindowView surfaceWindowView =
-                    new StartingSurfaceWindowView(context, iconSize);
-            surfaceWindowView.setBackground(new ColorDrawable(mThemeColor));
+            final SplashScreenView.Builder builder = new SplashScreenView.Builder(context);
+            builder.setIconSize(iconSize).setBackgroundColor(mThemeColor);
             if (iconDrawable != null) {
-                surfaceWindowView.setIconDrawable(iconDrawable);
+                builder.setCenterViewDrawable(iconDrawable);
             }
+            builder.setAnimationDuration(mIconAnimationDuration);
+            if (mBrandingDrawable != null) {
+                builder.setBrandingDrawable(mBrandingDrawable, mBrandingImageWidth,
+                        mBrandingImageHeight);
+            }
+            final SplashScreenView splashScreenView = builder.build();
             if (DEBUG) {
-                Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + surfaceWindowView);
+                Slog.d(TAG, "fillViewWithIcon surfaceWindowView " + splashScreenView);
             }
-            win.setContentView(surfaceWindowView);
-            makeSystemUIColorsTransparent(win);
-            return surfaceWindowView;
-        }
-
-        private void makeSystemUIColorsTransparent(PhoneWindow win) {
-            win.setStatusBarColor(Color.TRANSPARENT);
-            win.setNavigationBarColor(Color.TRANSPARENT);
+            win.setContentView(splashScreenView);
+            splashScreenView.cacheRootWindow(win);
+            splashScreenView.makeSystemUIColorsTransparent();
+            return splashScreenView;
         }
     }
 
@@ -298,8 +362,8 @@
         return root < 0.1;
     }
 
-    private static View makeSplashscreenContentDrawable(PhoneWindow win, Context ctx,
-            int splashscreenContentResId) {
+    private static SplashScreenView makeSplashscreenContentDrawable(Window win,
+            Context ctx, int splashscreenContentResId) {
         // doesn't support windowSplashscreenContent after S
         // TODO add an allowlist to skip some packages if needed
         final int targetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
@@ -316,7 +380,8 @@
         if (drawable == null) {
             return null;
         }
-        View view = new View(ctx);
+        SplashScreenView view = new SplashScreenView(ctx);
+        view.setNotCopyable();
         view.setBackground(drawable);
         win.setContentView(view);
         return view;
@@ -532,34 +597,4 @@
             }
         }
     }
-
-    private static class StartingSurfaceWindowView extends FrameLayout {
-        // TODO animate the icon view
-        private final View mIconView;
-
-        StartingSurfaceWindowView(Context context, int iconSize) {
-            super(context);
-
-            final boolean emptyIcon = iconSize == 0;
-            if (emptyIcon) {
-                mIconView = null;
-            } else {
-                mIconView = new View(context);
-                FrameLayout.LayoutParams params =
-                        new FrameLayout.LayoutParams(iconSize, iconSize);
-                params.gravity = Gravity.CENTER;
-                addView(mIconView, params);
-            }
-            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        }
-
-        // TODO support animatable icon
-        void setIconDrawable(Drawable icon) {
-            if (mIconView != null) {
-                mIconView.setBackground(icon);
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index f374922..5332291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -43,6 +43,8 @@
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.SplashScreenView;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.StartingWindowInfo;
 import android.window.TaskOrganizer;
 import android.window.TaskSnapshot;
@@ -63,7 +65,6 @@
  * class to remove the starting window of the Task.
  * @hide
  */
-
 public class StartingSurfaceDrawer {
     static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
     static final boolean DEBUG_SPLASH_SCREEN = false;
@@ -81,7 +82,10 @@
         mContext = context;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mMainExecutor = mainExecutor;
-        mSplashscreenContentDrawer = new SplashscreenContentDrawer(context);
+
+        final int maxIconAnimDuration = context.getResources().getInteger(
+                com.android.wm.shell.R.integer.max_starting_window_intro_icon_anim_duration);
+        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, maxIconAnimDuration);
     }
 
     private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
@@ -193,9 +197,8 @@
     public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
         final PreferredStartingTypeHelper helper =
                 new PreferredStartingTypeHelper(windowInfo);
-        final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
         if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) {
-            addSplashScreenStartingWindow(runningTaskInfo, appToken);
+            addSplashScreenStartingWindow(windowInfo, appToken);
         } else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) {
             final TaskSnapshot snapshot = helper.mSnapshot;
             makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
@@ -203,11 +206,13 @@
         // If prefer don't show, then don't show!
     }
 
-    private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) {
+    private void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+        final RunningTaskInfo taskInfo = windowInfo.taskInfo;
         final ActivityInfo activityInfo = taskInfo.topActivityInfo;
         if (activityInfo == null) {
             return;
         }
+
         final int displayId = taskInfo.displayId;
         if (activityInfo.packageName == null) {
             return;
@@ -222,11 +227,11 @@
         }
 
         Context context = mContext;
-        int theme = activityInfo.getThemeResource();
-        if (theme == 0) {
-            // replace with the default theme if the application didn't set
-            theme = com.android.internal.R.style.Theme_DeviceDefault_DayNight;
-        }
+        // replace with the default theme if the application didn't set
+        final int theme = windowInfo.splashScreenThemeResId != 0
+                ? windowInfo.splashScreenThemeResId
+                : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+                        : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
         if (DEBUG_SPLASH_SCREEN) {
             Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
                     + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
@@ -352,9 +357,10 @@
         }
 
         params.setTitle("Splash Screen " + activityInfo.packageName);
-        final View contentView = mSplashscreenContentDrawer.makeSplashScreenContentView(win,
-                context, iconRes, splashscreenContentResId[0]);
-        if (contentView == null) {
+        final SplashScreenView splashScreenView =
+                mSplashscreenContentDrawer.makeSplashScreenContentView(win, context, iconRes,
+                        splashscreenContentResId[0]);
+        if (splashScreenView == null) {
             Slog.w(TAG, "Adding splash screen window for " + activityInfo.packageName + " failed!");
             return;
         }
@@ -366,7 +372,7 @@
                     + activityInfo.packageName + " / " + appToken + ": " + view);
         }
         final WindowManager wm = context.getSystemService(WindowManager.class);
-        postAddWindow(taskInfo.taskId, appToken, view, wm, params);
+        postAddWindow(taskInfo.taskId, appToken, view, wm, params, splashScreenView);
     }
 
     /**
@@ -379,7 +385,7 @@
                 snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */);
         mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
         final StartingWindowRecord tView =
-                new StartingWindowRecord(null/* decorView */, surface);
+                new StartingWindowRecord(null/* decorView */, surface, null /* splashScreenView */);
         mStartingWindowRecords.put(taskId, tView);
     }
 
@@ -393,37 +399,60 @@
         removeWindowSynced(taskId);
     }
 
-    protected void postAddWindow(int taskId, IBinder appToken,
-        View view, WindowManager wm, WindowManager.LayoutParams params) {
-        boolean shouldSaveView = true;
-        try {
-            wm.addView(view, params);
-        } catch (WindowManager.BadTokenException e) {
-            // ignore
-            Slog.w(TAG, appToken + " already running, starting window not displayed. "
-                    + e.getMessage());
-            shouldSaveView = false;
-        } catch (RuntimeException e) {
-            // don't crash if something else bad happens, for example a
-            // failure loading resources because we are loading from an app
-            // on external storage that has been unmounted.
-            Slog.w(TAG, appToken + " failed creating starting window", e);
-            shouldSaveView = false;
-        } finally {
-            if (view != null && view.getParent() == null) {
-                Slog.w(TAG, "view not successfully added to wm, removing view");
-                wm.removeViewImmediate(view);
-                shouldSaveView = false;
-            }
+    /**
+     * Called when the Task wants to copy the splash screen.
+     * @param taskId
+     */
+    public void copySplashScreenView(int taskId) {
+        final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
+        SplashScreenViewParcelable parcelable;
+        if (preView != null && preView.mContentView != null
+                && preView.mContentView.isCopyable()) {
+            parcelable = new SplashScreenViewParcelable(preView.mContentView);
+        } else {
+            parcelable = null;
         }
+        if (DEBUG_SPLASH_SCREEN) {
+            Slog.v(TAG, "Copying splash screen window view for task: " + taskId
+                    + " parcelable? " + parcelable);
+        }
+        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+    }
 
-        if (shouldSaveView) {
-            removeWindowSynced(taskId);
-            mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
-            final StartingWindowRecord tView =
-                    new StartingWindowRecord(view, null /* TaskSnapshotWindow */);
-            mStartingWindowRecords.put(taskId, tView);
-        }
+    protected void postAddWindow(int taskId, IBinder appToken,
+            View view, WindowManager wm, WindowManager.LayoutParams params,
+            SplashScreenView splashScreenView) {
+        mMainExecutor.execute(() -> {
+            boolean shouldSaveView = true;
+            try {
+                wm.addView(view, params);
+            } catch (WindowManager.BadTokenException e) {
+                // ignore
+                Slog.w(TAG, appToken + " already running, starting window not displayed. "
+                        + e.getMessage());
+                shouldSaveView = false;
+            } catch (RuntimeException e) {
+                // don't crash if something else bad happens, for example a
+                // failure loading resources because we are loading from an app
+                // on external storage that has been unmounted.
+                Slog.w(TAG, appToken + " failed creating starting window", e);
+                shouldSaveView = false;
+            } finally {
+                if (view != null && view.getParent() == null) {
+                    Slog.w(TAG, "view not successfully added to wm, removing view");
+                    wm.removeViewImmediate(view);
+                    shouldSaveView = false;
+                }
+            }
+            if (shouldSaveView) {
+                removeWindowSynced(taskId);
+                mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+                final StartingWindowRecord tView = new StartingWindowRecord(view,
+                        null /* TaskSnapshotWindow */, splashScreenView);
+                splashScreenView.startIntroAnimation();
+                mStartingWindowRecords.put(taskId, tView);
+            }
+        });
     }
 
     protected void removeWindowSynced(int taskId) {
@@ -459,10 +488,13 @@
     private static class StartingWindowRecord {
         private final View mDecorView;
         private final TaskSnapshotWindow mTaskSnapshotWindow;
+        private final SplashScreenView mContentView;
 
-        StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) {
+        StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow,
+                SplashScreenView splashScreenView) {
             mDecorView = decorView;
             mTaskSnapshotWindow = taskSnapshotWindow;
+            mContentView = splashScreenView;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index a6f44ef..b7fd3cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -43,6 +43,7 @@
 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
 import static com.android.internal.policy.DecorView.getNavigationBarRect;
 
+import android.annotation.BinderThread;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
@@ -498,7 +499,7 @@
         }
     }
 
-    @ExternalThread
+    @BinderThread
     static class Window extends BaseIWindow {
         private TaskSnapshotWindow mOuter;
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 111362a..ecc066b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -24,7 +24,6 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
-import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
 import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -113,7 +112,7 @@
         if (isTelevision) {
             uiDevice.closeTvPipWindow()
         } else {
-            uiDevice.closePipWindow()
+            closePipWindow(WindowManagerStateHelper(mInstrumentation))
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
index a14b46e..d56ed02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -50,8 +48,7 @@
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val testApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         testApp.launchViaIntent(wmHelper)
@@ -97,7 +94,7 @@
                     }
                 }
             }
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 99a40da..ff31ba7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
@@ -50,9 +48,8 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(
-                eachRun = true, stringExtras = emptyMap())
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true,
+                stringExtras = emptyMap()) { configuration ->
                 transitions {
                     pipApp.clickEnterPipButton()
                     pipApp.expandPipWindow(wmHelper)
@@ -92,7 +89,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 7576e24..f054e64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.startRotation
@@ -48,8 +46,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val imeApp = ImeAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         imeApp.launchViaIntent(wmHelper)
@@ -90,7 +87,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
-                baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index adab5e8..ade65ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
@@ -55,8 +53,7 @@
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val fixedApp = FixedAppHelper(instrumentation)
-            val baseConfig = getTransitionLaunch(eachRun = false)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = false) { configuration ->
                 setup {
                     test {
                         fixedApp.launchViaIntent(wmHelper)
@@ -112,8 +109,7 @@
             }
 
             return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
-                baseConfig, testSpec,
-                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
+                testSpec, supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
                 repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 4b826ff..f2d5899 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -110,7 +107,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index 62e8221..1b44377 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.os.Bundle
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.focusChanges
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
@@ -52,8 +50,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val baseConfig = getTransitionLaunch(eachRun = true)
-            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+            val testSpec = getTransition(eachRun = true) { configuration ->
                 setup {
                     eachRun {
                         this.setRotation(configuration.startRotation)
@@ -111,7 +108,7 @@
                 }
             }
 
-            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
                 testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
index eb7bae1..b1e404e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt
@@ -22,8 +22,6 @@
 import android.view.Surface
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.closePipWindow
-import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.repetitions
@@ -83,10 +81,6 @@
                 }
                 test {
                     removeAllTasksButHome()
-
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
                     pipApp.exit()
                 }
             }
@@ -98,11 +92,13 @@
      *
      * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
      * @param stringExtras Arguments to pass to the PIP launch intent
+     * @param extraSpec Addicional segment of flicker specification
      */
     @JvmOverloads
-    fun getTransitionLaunch(
+    open fun getTransition(
         eachRun: Boolean,
-        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true")
+        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+        extraSpec: FlickerBuilder.(Bundle) -> Unit = {}
     ): FlickerBuilder.(Bundle) -> Unit {
         return { configuration ->
             setupAndTeardown(this, configuration)
@@ -135,6 +131,8 @@
                     removeAllTasksButHome()
                 }
             }
+
+            extraSpec(this, configuration)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 80ea9b9..a0e9f43 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -51,7 +52,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -76,7 +78,9 @@
     @Mock
     private Context mContext;
     @Mock
-    private SizeCompatUI mSizeCompatUI;
+    private SizeCompatUIController mSizeCompatUI;
+    @Mock
+    private StartingSurfaceDrawer mStartingSurfaceDrawer;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -112,7 +116,7 @@
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
         mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
-                mSizeCompatUI));
+                mSizeCompatUI, mStartingSurfaceDrawer));
     }
 
     @Test
@@ -279,10 +283,10 @@
 
         // sizeCompatActivity is null if top activity is not in size compat.
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration.windowConfiguration.getBounds(),
-                null /* sizeCompatActivity*/ , taskListener);
+                null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
 
         // sizeCompatActivity is non-null if top activity is in size compat.
+        clearInvocations(mSizeCompatUI);
         final RunningTaskInfo taskInfo2 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
         taskInfo2.displayId = taskInfo1.displayId;
@@ -290,14 +294,12 @@
         taskInfo2.topActivityInSizeCompat = true;
         mOrganizer.onTaskInfoChanged(taskInfo2);
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                taskInfo1.configuration.windowConfiguration.getBounds(),
-                taskInfo1.topActivityToken,
-                taskListener);
+                taskInfo1.configuration, taskInfo1.topActivityToken, taskListener);
 
+        clearInvocations(mSizeCompatUI);
         mOrganizer.onTaskVanished(taskInfo1);
         verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
-                null /* taskConfig */, null /* sizeCompatActivity*/,
-                null /* taskListener */);
+                null /* taskConfig */, null /* sizeCompatActivity*/, null /* taskListener */);
     }
 
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9430af9..d10c036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -44,6 +44,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
 import org.junit.Before;
@@ -70,7 +71,7 @@
     @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
-    @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
+    @Mock private Optional<LegacySplitScreenController> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 449ad88..19930485 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -22,16 +22,14 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.Size;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -59,6 +57,9 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class PipTouchHandlerTest extends ShellTestCase {
 
+    private static final int INSET = 10;
+    private static final int PIP_LENGTH = 100;
+
     private PipTouchHandler mPipTouchHandler;
 
     @Mock
@@ -85,8 +86,9 @@
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
 
+    private DisplayLayout mDisplayLayout;
     private Rect mInsetBounds;
-    private Rect mMinBounds;
+    private Rect mPipBounds;
     private Rect mCurBounds;
     private boolean mFromImeAdjustment;
     private boolean mFromShelfAdjustment;
@@ -109,12 +111,18 @@
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
         mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler);
 
-        // Assume a display of 1000 x 1000
-        // inset of 10
-        mInsetBounds = new Rect(10, 10, 990, 990);
+        mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipBoundsState.setDisplayLayout(mDisplayLayout);
+        mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
+                mPipBoundsState.getDisplayBounds().top + INSET,
+                mPipBoundsState.getDisplayBounds().right - INSET,
+                mPipBoundsState.getDisplayBounds().bottom - INSET);
         // minBounds of 100x100 bottom right corner
-        mMinBounds = new Rect(890, 890, 990, 990);
-        mCurBounds = new Rect(mMinBounds);
+        mPipBounds = new Rect(mPipBoundsState.getDisplayBounds().right - INSET - PIP_LENGTH,
+                mPipBoundsState.getDisplayBounds().bottom - INSET - PIP_LENGTH,
+                mPipBoundsState.getDisplayBounds().right - INSET,
+                mPipBoundsState.getDisplayBounds().bottom - INSET);
+        mCurBounds = new Rect(mPipBounds);
         mFromImeAdjustment = false;
         mFromShelfAdjustment = false;
         mDisplayRotation = 0;
@@ -122,37 +130,23 @@
     }
 
     @Test
-    public void updateMovementBounds_minBounds() {
-        Rect expectedMinMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getMovementBounds(mMinBounds, mInsetBounds, expectedMinMovementBounds,
+    public void updateMovementBounds_minMaxBounds() {
+        final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
+                mPipBoundsState.getDisplayBounds().height());
+        Rect expectedMovementBounds = new Rect();
+        mPipBoundsAlgorithm.getMovementBounds(mPipBounds, mInsetBounds, expectedMovementBounds,
                 0);
 
-        mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
+        mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
                 mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
 
-        assertEquals(expectedMinMovementBounds, mPipBoundsState.getNormalMovementBounds());
+        assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
         verify(mPipResizeGestureHandler, times(1))
-                .updateMinSize(mMinBounds.width(), mMinBounds.height());
-    }
+                .updateMinSize(mPipBounds.width(), mPipBounds.height());
 
-    @Test
-    public void updateMovementBounds_maxBounds() {
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        Size maxSize = mPipBoundsAlgorithm.getSizeForAspectRatio(1,
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.pip_expanded_shortest_edge_size), displaySize.x, displaySize.y);
-        Rect maxBounds = new Rect(0, 0, maxSize.getWidth(), maxSize.getHeight());
-        Rect expectedMaxMovementBounds = new Rect();
-        mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, expectedMaxMovementBounds,
-                0);
-
-        mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
-                mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
-
-        assertEquals(expectedMaxMovementBounds, mPipBoundsState.getExpandedMovementBounds());
         verify(mPipResizeGestureHandler, times(1))
-                .updateMaxSize(maxBounds.width(), maxBounds.height());
+                .updateMaxSize(shorterLength - 2 * mInsetBounds.left,
+                        shorterLength - 2 * mInsetBounds.left);
     }
 
     @Test
@@ -160,7 +154,7 @@
         mFromImeAdjustment = true;
         mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
 
-        mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
+        mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
                 mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
 
         verify(mMotionHelper, times(1)).animateToOffset(any(), anyInt());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
new file mode 100644
index 0000000..d9086a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.wm.shell.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatRestartButton}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatRestartButtonTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatRestartButtonTest extends ShellTestCase {
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private IBinder mActivityToken;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private DisplayLayout mDisplayLayout;
+
+    private SizeCompatUILayout mLayout;
+    private SizeCompatRestartButton mButton;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final int taskId = 1;
+        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+                taskId, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+        mButton = (SizeCompatRestartButton)
+                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
+        mButton.inject(mLayout);
+
+        spyOn(mLayout);
+        spyOn(mButton);
+        doNothing().when(mButton).showHint();
+    }
+
+    @Test
+    public void testOnClick() {
+        doNothing().when(mLayout).onRestartButtonClicked();
+
+        mButton.onClick(mButton);
+
+        verify(mLayout).onRestartButtonClicked();
+    }
+
+    @Test
+    public void testOnLongClick() {
+        verify(mButton, never()).showHint();
+
+        mButton.onLongClick(mButton);
+
+        verify(mButton).showHint();
+    }
+
+    @Test
+    public void testOnAttachedToWindow_showHint() {
+        mLayout.mShouldShowHint = false;
+        mButton.onAttachedToWindow();
+
+        verify(mButton, never()).showHint();
+
+        mLayout.mShouldShowHint = true;
+        mButton.onAttachedToWindow();
+
+        verify(mButton).showHint();
+    }
+
+    @Test
+    public void testRemove() {
+        mButton.remove();
+
+        verify(mButton).dismissHint();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index 98f01ff..806a90b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -16,23 +16,28 @@
 
 package com.android.wm.shell.sizecompatui;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.graphics.Rect;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,28 +55,34 @@
 @SmallTest
 public class SizeCompatUIControllerTest extends ShellTestCase {
     private static final int DISPLAY_ID = 0;
-
-    private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+    private static final int TASK_ID = 12;
 
     private SizeCompatUIController mController;
     private @Mock DisplayController mMockDisplayController;
+    private @Mock DisplayLayout mMockDisplayLayout;
     private @Mock DisplayImeController mMockImeController;
-    private @Mock SizeCompatRestartButton mMockButton;
     private @Mock IBinder mMockActivityToken;
     private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+    private @Mock SyncTransactionQueue mMockSyncQueue;
+    private @Mock SizeCompatUILayout mMockLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doReturn(true).when(mMockButton).show();
 
+        doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
+        doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
+        doReturn(TASK_ID).when(mMockLayout).getTaskId();
         mController = new SizeCompatUIController(mContext, mMockDisplayController,
-                mMockImeController, mShellMainExecutor) {
+                mMockImeController, mMockSyncQueue) {
             @Override
-            SizeCompatRestartButton createRestartButton(Context context, int displayId) {
-                return mMockButton;
+            SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+                    Configuration taskConfig, IBinder activityToken,
+                    ShellTaskOrganizer.TaskListener taskListener) {
+                return mMockLayout;
             }
         };
+        spyOn(mController);
     }
 
     @Test
@@ -82,42 +93,72 @@
 
     @Test
     public void testOnSizeCompatInfoChanged() {
-        final int taskId = 12;
-        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Configuration taskConfig = new Configuration();
 
-        // Verify that the restart button is added with non-null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        // Verify that the restart button is added with non-null size compat info.
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockActivityToken, mMockTaskListener);
-        mShellMainExecutor.flushAll();
 
-        verify(mMockButton).show();
-        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
+        verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
+                eq(mMockActivityToken), eq(mMockTaskListener));
 
-        // Verify that the restart button is removed with null size compat activity.
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+        // Verify that the restart button is updated with non-null new size compat info.
+        final Configuration newTaskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig,
+                mMockActivityToken, mMockTaskListener);
 
-        mShellMainExecutor.flushAll();
-        verify(mMockButton).remove();
+        verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockActivityToken, mMockTaskListener,
+                false /* isImeShowing */);
+
+        // Verify that the restart button is removed with null size compat info.
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, null, mMockTaskListener);
+
+        verify(mMockLayout).release();
+    }
+
+    @Test
+    public void testOnDisplayRemoved() {
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+                mMockActivityToken, mMockTaskListener);
+
+        mController.onDisplayRemoved(DISPLAY_ID + 1);
+
+        verify(mMockLayout, never()).release();
+
+        mController.onDisplayRemoved(DISPLAY_ID);
+
+        verify(mMockLayout).release();
+    }
+
+    @Test
+    public void testOnDisplayConfigurationChanged() {
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+                mMockActivityToken, mMockTaskListener);
+
+        final Configuration newTaskConfig = new Configuration();
+        mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, newTaskConfig);
+
+        verify(mMockLayout, never()).updateDisplayLayout(any());
+
+        mController.onDisplayConfigurationChanged(DISPLAY_ID, newTaskConfig);
+
+        verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
     }
 
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
-        final int taskId = 12;
-        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
-        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+        final Configuration taskConfig = new Configuration();
+        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockActivityToken, mMockTaskListener);
-        mShellMainExecutor.flushAll();
 
-        // Verify that the restart button is hidden when IME is visible.
-        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
 
-        verify(mMockButton).setVisibility(eq(View.GONE));
+        verify(mMockLayout).updateImeVisibility(true);
 
-        // Verify that the restart button is visible when IME is hidden.
-        doReturn(View.GONE).when(mMockButton).getVisibility();
         mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
 
-        verify(mMockButton).setVisibility(eq(View.VISIBLE));
+        verify(mMockLayout).updateImeVisibility(false);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
new file mode 100644
index 0000000..236db44
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.wm.shell.sizecompatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityClient;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUILayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatUILayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUILayoutTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private IBinder mActivityToken;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private DisplayLayout mDisplayLayout;
+    @Mock private SizeCompatRestartButton mButton;
+    private Configuration mTaskConfig;
+
+    private SizeCompatUILayout mLayout;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskConfig = new Configuration();
+
+        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mContext, new Configuration(),
+                TASK_ID, mActivityToken, mTaskListener, mDisplayLayout, false /* hasShownHint*/);
+
+        spyOn(mLayout);
+        spyOn(mLayout.mWindowManager);
+        doReturn(mButton).when(mLayout.mWindowManager).createSizeCompatUI();
+    }
+
+    @Test
+    public void testCreateSizeCompatButton() {
+        // Not create button if IME is showing.
+        mLayout.createSizeCompatButton(true /* isImeShowing */);
+
+        verify(mLayout.mWindowManager, never()).createSizeCompatUI();
+        assertNull(mLayout.mButton);
+
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        verify(mLayout.mWindowManager).createSizeCompatUI();
+        assertNotNull(mLayout.mButton);
+    }
+
+    @Test
+    public void testRelease() {
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        mLayout.release();
+
+        assertNull(mLayout.mButton);
+        verify(mButton).remove();
+        verify(mLayout.mWindowManager).release();
+    }
+
+    @Test
+    public void testUpdateSizeCompatInfo() {
+        mLayout.createSizeCompatButton(false /* isImeShowing */);
+
+        // No diff
+        clearInvocations(mLayout);
+        mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, mTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout, never()).updateSurfacePosition();
+        verify(mLayout, never()).release();
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+
+        // Change task listener, recreate button.
+        clearInvocations(mLayout);
+        final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+                ShellTaskOrganizer.TaskListener.class);
+        mLayout.updateSizeCompatInfo(mTaskConfig, mActivityToken, newTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout).release();
+        verify(mLayout).createSizeCompatButton(anyBoolean());
+
+        // Change task bounds, update position.
+        clearInvocations(mLayout);
+        final Configuration newTaskConfiguration = new Configuration();
+        newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+        mLayout.updateSizeCompatInfo(newTaskConfiguration, mActivityToken, newTaskListener,
+                false /* isImeShowing */);
+
+        verify(mLayout).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateDisplayLayout() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+                mContext.getResources(), false, false);
+
+        mLayout.updateDisplayLayout(displayLayout1);
+        verify(mLayout).updateSurfacePosition();
+
+        // No update if the display bounds is the same.
+        clearInvocations(mLayout);
+        final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+                mContext.getResources(), false, false);
+        mLayout.updateDisplayLayout(displayLayout2);
+        verify(mLayout, never()).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateImeVisibility() {
+        // Create button if it is not created.
+        mLayout.mButton = null;
+        mLayout.updateImeVisibility(false /* isImeShowing */);
+
+        verify(mLayout).createSizeCompatButton(false /* isImeShowing */);
+
+        // Hide button if ime is shown.
+        clearInvocations(mLayout);
+        doReturn(View.VISIBLE).when(mButton).getVisibility();
+        mLayout.updateImeVisibility(true /* isImeShowing */);
+
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+        verify(mButton).setVisibility(View.GONE);
+
+        // Show button if ime is not shown.
+        doReturn(View.GONE).when(mButton).getVisibility();
+        mLayout.updateImeVisibility(false /* isImeShowing */);
+
+        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
+        verify(mButton).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testAttachToParentSurface() {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder();
+        mLayout.attachToParentSurface(b);
+
+        verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+    }
+
+    @Test
+    public void testOnRestartButtonClicked() {
+        spyOn(ActivityClient.getInstance());
+        doNothing().when(ActivityClient.getInstance()).restartActivityProcessIfVisible(any());
+
+        mLayout.onRestartButtonClicked();
+
+        verify(ActivityClient.getInstance()).restartActivityProcessIfVisible(mActivityToken);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index c9537af..de7d6c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -40,6 +40,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
+import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,7 +80,8 @@
 
         @Override
         protected void postAddWindow(int taskId, IBinder appToken,
-                View view, WindowManager wm, WindowManager.LayoutParams params) {
+                View view, WindowManager wm, WindowManager.LayoutParams params,
+                SplashScreenView splashScreenView) {
             // listen for addView
             mAddWindowForTask = taskId;
             mViewThemeResId = view.getContext().getThemeResId();
@@ -125,7 +127,8 @@
                 createWindowInfo(taskId, android.R.style.Theme);
         mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
         waitHandlerIdle(mainLoop);
-        verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+        verify(mStartingSurfaceDrawer).postAddWindow(
+                eq(taskId), eq(mBinder), any(), any(), any(), any());
         assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
 
         mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId);
@@ -142,7 +145,8 @@
                 createWindowInfo(taskId, 0);
         mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder);
         waitHandlerIdle(mainLoop);
-        verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+        verify(mStartingSurfaceDrawer).postAddWindow(
+                eq(taskId), eq(mBinder), any(), any(), any(), any());
         assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
     }
 
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index ca5981c0..9c743ce 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -83,8 +83,16 @@
     return {};
   }
 
+  std::unique_ptr<AssetsProvider> overlay_assets;
   const std::string overlay_path(loaded_idmap->OverlayApkPath());
-  auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
+  if (IsFabricatedOverlay(overlay_path)) {
+    // Fabricated overlays do not contain resource definitions. All of the overlay resource values
+    // are defined inline in the idmap.
+    overlay_assets = EmptyAssetsProvider::Create();
+  } else {
+    // The overlay should be an APK.
+    overlay_assets = ZipAssetsProvider::Create(overlay_path);
+  }
   if (overlay_assets == nullptr) {
     return {};
   }
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 03ab62f..36bde5c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -102,9 +102,8 @@
   memset(&configuration_, 0, sizeof(configuration_));
 }
 
-bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
-                                 bool invalidate_caches) {
-  apk_assets_ = apk_assets;
+bool AssetManager2::SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches) {
+  apk_assets_ = std::move(apk_assets);
   BuildDynamicRefTable();
   RebuildFilterList();
   if (invalidate_caches) {
@@ -137,6 +136,36 @@
   // 0x01 is reserved for the android package.
   int next_package_id = 0x02;
   for (const ApkAssets* apk_assets : sorted_apk_assets) {
+    std::shared_ptr<OverlayDynamicRefTable> overlay_ref_table;
+    if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
+      // The target package must precede the overlay package in the apk assets paths in order
+      // to take effect.
+      auto iter = apk_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+      if (iter == apk_assets_package_ids.end()) {
+         LOG(INFO) << "failed to find target package for overlay "
+                   << loaded_idmap->OverlayApkPath();
+      } else {
+        uint8_t target_package_id = iter->second;
+
+        // Create a special dynamic reference table for the overlay to rewrite references to
+        // overlay resources as references to the target resources they overlay.
+        overlay_ref_table = std::make_shared<OverlayDynamicRefTable>(
+            loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
+
+        // Add the overlay resource map to the target package's set of overlays.
+        const uint8_t target_idx = package_ids_[target_package_id];
+        CHECK(target_idx != 0xff) << "overlay target '" << loaded_idmap->TargetApkPath()
+                                  << "'added to apk_assets_package_ids but does not have an"
+                                  << " assigned package group";
+
+        PackageGroup& target_package_group = package_groups_[target_idx];
+        target_package_group.overlays_.push_back(
+            ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
+                                                                  overlay_ref_table.get()),
+                              apk_assets_cookies[apk_assets]});
+      }
+    }
+
     const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
     for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
       // Get the package ID or assign one if a shared library.
@@ -147,50 +176,25 @@
         package_id = package->GetPackageId();
       }
 
-      // Add the mapping for package ID to index if not present.
       uint8_t idx = package_ids_[package_id];
       if (idx == 0xff) {
+        // Add the mapping for package ID to index if not present.
         package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
-        package_groups_.push_back({});
+        PackageGroup& new_group = package_groups_.emplace_back();
 
-        if (apk_assets->IsOverlay()) {
-          // The target package must precede the overlay package in the apk assets paths in order
-          // to take effect.
-          const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
-          auto target_package_iter = apk_assets_package_ids.find(
-              std::string(loaded_idmap->TargetApkPath()));
-          if (target_package_iter == apk_assets_package_ids.end()) {
-             LOG(INFO) << "failed to find target package for overlay "
-                       << loaded_idmap->OverlayApkPath();
-          } else {
-            const uint8_t target_package_id = target_package_iter->second;
-            const uint8_t target_idx = package_ids_[target_package_id];
-            CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
-                                      << " have an assigned package group";
-
-            PackageGroup& target_package_group = package_groups_[target_idx];
-
-            // Create a special dynamic reference table for the overlay to rewrite references to
-            // overlay resources as references to the target resources they overlay.
-            auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
-                loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
-            package_groups_.back().dynamic_ref_table = overlay_table;
-
-            // Add the overlay resource map to the target package's set of overlays.
-            target_package_group.overlays_.push_back(
-                ConfiguredOverlay{loaded_idmap->GetTargetResourcesMap(target_package_id,
-                                                                      overlay_table.get()),
-                                  apk_assets_cookies[apk_assets]});
-          }
+        if (overlay_ref_table != nullptr) {
+          // If this package is from an overlay, use a dynamic reference table that can rewrite
+          // overlay resource ids to their corresponding target resource ids.
+          new_group.dynamic_ref_table = overlay_ref_table;
         }
 
-        DynamicRefTable* ref_table = package_groups_.back().dynamic_ref_table.get();
+        DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
         ref_table->mAssignedPackageId = package_id;
         ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
-      PackageGroup* package_group = &package_groups_[idx];
 
       // Add the package and to the set of packages with the same ID.
+      PackageGroup* package_group = &package_groups_[idx];
       package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
       package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
 
@@ -578,7 +582,7 @@
 
   const PackageGroup& package_group = package_groups_[package_idx];
   auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
-                                 stop_at_first_match, ignore_configuration);
+                                  stop_at_first_match, ignore_configuration);
   if (UNLIKELY(!result.has_value())) {
     return base::unexpected(result.error());
   }
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 23cacf8..f3c48f7 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -84,7 +84,7 @@
   return value_;
 }
 
-ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
+ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
                                      time_t last_mod_time)
     : zip_handle_(handle, ::CloseArchive),
       name_(std::forward<PathOrDebugName>(path)),
@@ -93,7 +93,7 @@
 std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
   ZipArchiveHandle handle;
   if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
-    LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+    LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
     CloseArchive(handle);
     return {};
   }
@@ -253,6 +253,14 @@
     return result == -1;
 }
 
+std::optional<uint32_t> ZipAssetsProvider::GetCrc(std::string_view path) const {
+  ::ZipEntry entry;
+  if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
+    return {};
+  }
+  return entry.crc32;
+}
+
 const std::string& ZipAssetsProvider::GetDebugName() const {
   return name_.GetDebugName();
 }
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index f216f55..efd1f6a 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -54,12 +54,6 @@
 };
 
 struct Idmap_data_header {
-  uint8_t target_package_id;
-  uint8_t overlay_package_id;
-
-  // Padding to ensure 4 byte alignment for target_entry_count
-  uint16_t p0;
-
   uint32_t target_entry_count;
   uint32_t target_inline_entry_count;
   uint32_t overlay_entry_count;
@@ -158,19 +152,19 @@
     return {};
   }
 
-  // The resource ids encoded within the idmap are build-time resource ids.
-  target_res_id = (0x00FFFFFFU & target_res_id)
-      | (((uint32_t) data_header_->target_package_id) << 24U);
+  // The resource ids encoded within the idmap are build-time resource ids so do not consider the
+  // package id when determining if the resource in the target package is overlaid.
+  target_res_id &= 0x00FFFFFFU;
 
   // Check if the target resource is mapped to an overlay resource.
   auto first_entry = entries_;
   auto end_entry = entries_ + dtohl(data_header_->target_entry_count);
   auto entry = std::lower_bound(first_entry, end_entry, target_res_id,
-                                [](const Idmap_target_entry &e, const uint32_t target_id) {
-    return dtohl(e.target_id) < target_id;
+                                [](const Idmap_target_entry& e, const uint32_t target_id) {
+    return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
   });
 
-  if (entry != end_entry && dtohl(entry->target_id) == target_res_id) {
+  if (entry != end_entry && (0x00FFFFFFU & dtohl(entry->target_id)) == target_res_id) {
     uint32_t overlay_resource_id = dtohl(entry->overlay_id);
     // Lookup the resource without rewriting the overlay resource id back to the target resource id
     // being looked up.
@@ -182,12 +176,13 @@
   auto first_inline_entry = inline_entries_;
   auto end_inline_entry = inline_entries_ + dtohl(data_header_->target_inline_entry_count);
   auto inline_entry = std::lower_bound(first_inline_entry, end_inline_entry, target_res_id,
-                                       [](const Idmap_target_entry_inline &e,
+                                       [](const Idmap_target_entry_inline& e,
                                           const uint32_t target_id) {
-    return dtohl(e.target_id) < target_id;
+    return (0x00FFFFFFU & dtohl(e.target_id)) < target_id;
   });
 
-  if (inline_entry != end_inline_entry && dtohl(inline_entry->target_id) == target_res_id) {
+  if (inline_entry != end_inline_entry &&
+      (0x00FFFFFFU & dtohl(inline_entry->target_id)) == target_res_id) {
     return Result(inline_entry->value);
   }
   return {};
@@ -235,7 +230,7 @@
   }
   return std::string_view(data, *len);
 }
-}
+} // namespace
 
 LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
                          const Idmap_header* header,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2233827..30500ab 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <fstream>
 #include <limits>
 #include <map>
 #include <memory>
@@ -44,6 +45,7 @@
 
 #ifdef __ANDROID__
 #include <binder/TextOutput.h>
+
 #endif
 
 #ifndef INT32_MAX
@@ -233,6 +235,15 @@
     fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
 }
 
+bool IsFabricatedOverlay(const std::string& path) {
+  std::ifstream fin(path);
+  uint32_t magic;
+  if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
+    return magic == kFabricatedOverlayMagic;
+  }
+  return false;
+}
+
 static bool assertIdmapHeader(const void* idmap, size_t size) {
     if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
         ALOGE("idmap: header is not word aligned");
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 6fbd6aa..2255973f 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -101,7 +101,7 @@
   // Only pass invalidate_caches=false when it is known that the structure
   // change in ApkAssets is due to a safe addition of resources with completely
   // new resource IDs.
-  bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
+  bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true);
 
   inline const std::vector<const ApkAssets*> GetApkAssets() const {
     return apk_assets_;
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 7b06947..6f16ff4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -88,6 +88,8 @@
   WARN_UNUSED const std::string& GetDebugName() const override;
   WARN_UNUSED bool IsUpToDate() const override;
 
+  WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
+
   ~ZipAssetsProvider() override = default;
  protected:
   std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 0ded793..6804472 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -168,15 +168,14 @@
   }
 
   // Returns a mapping from target resource ids to overlay values.
-  const IdmapResMap GetTargetResourcesMap(
-      uint8_t target_assigned_package_id, const OverlayDynamicRefTable* overlay_ref_table) const {
+  const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
+                                          const OverlayDynamicRefTable* overlay_ref_table) const {
     return IdmapResMap(data_header_, target_entries_, target_inline_entries_,
                        target_assigned_package_id, overlay_ref_table);
   }
 
   // Returns a dynamic reference table for a loaded overlay package.
-  const OverlayDynamicRefTable GetOverlayDynamicRefTable(
-      uint8_t target_assigned_package_id) const {
+  const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
 
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index bfd564c..168a863 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -43,8 +43,19 @@
 
 namespace android {
 
-constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000007u;
+constexpr const uint32_t kIdmapMagic = 0x504D4449u;
+constexpr const uint32_t kIdmapCurrentVersion = 0x00000008u;
+
+// This must never change.
+constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endian)
+
+// The version should only be changed when a backwards-incompatible change must be made to the
+// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
+// to prevent losing fabricated overlay data.
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+
+// Returns whether or not the path represents a fabricated overlay.
+bool IsFabricatedOverlay(const std::string& path);
 
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 723413c..88eadcc 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 74cf1fd..20a8a8c 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -160,10 +160,6 @@
         }
     }
 
-    if (!mHasStartValue) {
-        doSetStartValue(getValue(mTarget));
-    }
-
     if (!mStagingRequests.empty()) {
         // No interpolator was set, use the default
         if (mPlayState == PlayState::NotStarted && !mInterpolator) {
@@ -270,9 +266,11 @@
     // to call setValue even if the animation isn't yet running or is still
     // being delayed as we need to override the staging value
     if (playTime < 0) {
-        setValue(mTarget, mFromValue);
         return false;
     }
+    if (!this->mHasStartValue) {
+        doSetStartValue(getValue(mTarget));
+    }
 
     float fraction = 1.0f;
     if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 27be622..d5fee3f 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -35,7 +35,6 @@
 
 public:
     static DeviceInfo* get();
-    static float getMaxRefreshRate() { return get()->mMaxRefreshRate; }
     static int32_t getWidth() { return get()->mWidth; }
     static int32_t getHeight() { return get()->mHeight; }
     // Gets the density in density-independent pixels
@@ -45,7 +44,6 @@
     static int64_t getAppOffset() { return get()->mAppVsyncOffsetNanos; }
     // Sets the density in density-independent pixels
     static void setDensity(float density) { sDensity.store(density); }
-    static void setMaxRefreshRate(float refreshRate) { get()->mMaxRefreshRate = refreshRate; }
     static void setWidth(int32_t width) { get()->mWidth = width; }
     static void setHeight(int32_t height) { get()->mHeight = height; }
     static void setRefreshRate(float refreshRate) {
@@ -91,7 +89,6 @@
     SkColorType mWideColorType = SkColorType::kN32_SkColorType;
     int mDisplaysSize = 0;
     int mPhysicalDisplayIndex = -1;
-    float mMaxRefreshRate = 60.0;
     int32_t mWidth = 1080;
     int32_t mHeight = 1920;
     int64_t mVsyncPeriod = 16666666;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e798f2a..971a53a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -79,7 +79,6 @@
 bool Properties::isolatedProcess = false;
 
 int Properties::contextPriority = 0;
-int Properties::defaultRenderAhead = -1;
 float Properties::defaultSdrWhitePoint = 200.f;
 
 bool Properties::load() {
@@ -129,10 +128,6 @@
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
 
-    defaultRenderAhead = std::max(
-            -1,
-            std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD, render_ahead().value_or(-1))));
-
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1639143..dcb79ba 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -162,8 +162,6 @@
  */
 #define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
 
-#define PROPERTY_RENDERAHEAD "debug.hwui.render_ahead"
-
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -247,8 +245,6 @@
 
     static int contextPriority;
 
-    static int defaultRenderAhead;
-
     static float defaultSdrWhitePoint;
 
 private:
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 0e338f3..2db3ace 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,10 +30,11 @@
 
 namespace android {
 
-MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
-                                 std::string_view filePath, int ttcIndex,
+MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
+                                 size_t fontSize, std::string_view filePath, int ttcIndex,
                                  const std::vector<minikin::FontVariation>& axes)
         : mTypeface(std::move(typeface))
+        , mSourceId(sourceId)
         , mFontData(fontData)
         , mFontSize(fontSize)
         , mTtcIndex(ttcIndex)
@@ -141,8 +142,8 @@
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
 
-    return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath,
-                                             ttcIndex, variations);
+    return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
+                                             mFilePath, ttcIndex, variations);
 }
 
 // hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 77a2142..de9a5c2 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -30,7 +30,7 @@
 
 class ANDROID_API MinikinFontSkia : public minikin::MinikinFont {
 public:
-    MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
+    MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
                     std::string_view filePath, int ttcIndex,
                     const std::vector<minikin::FontVariation>& axes);
 
@@ -62,6 +62,7 @@
     const std::vector<minikin::FontVariation>& GetAxes() const;
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
             const std::vector<minikin::FontVariation>&) const;
+    int GetSourceId() const override { return mSourceId; }
 
     static uint32_t packFontFlags(const SkFont&);
     static void unpackFontFlags(SkFont*, uint32_t fontFlags);
@@ -73,6 +74,7 @@
 private:
     sk_sp<SkTypeface> mTypeface;
 
+    int mSourceId;
     // A raw pointer to the font data - it should be owned by some other object with
     // lifetime at least as long as this object.
     const void* mFontData;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 03f1d62..5a9d250 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -185,9 +185,9 @@
     sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
-    std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
-            std::move(typeface), data, st.st_size, kRobotoFont, 0,
-            std::vector<minikin::FontVariation>());
+    std::shared_ptr<minikin::MinikinFont> font =
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
+                                              0, std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 2e85840..ce5ac38 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -17,15 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "Minikin"
 
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include "FontUtils.h"
+#include "GraphicsJNI.h"
 #include "SkData.h"
 #include "SkFontMgr.h"
 #include "SkRefCnt.h"
 #include "SkTypeface.h"
-#include "GraphicsJNI.h"
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
 #include "Utils.h"
-#include "FontUtils.h"
+#include "fonts/Font.h"
 
 #include <hwui/MinikinSkia.h>
 #include <hwui/Typeface.h>
@@ -35,6 +36,12 @@
 
 #include <memory>
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// The following JNI methods are kept only for compatibility reasons due to hidden API accesses.
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 namespace android {
 
 struct NativeFamilyBuilder {
@@ -125,8 +132,8 @@
         return false;
     }
     std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, "", ttcIndex,
-                    builder->axes);
+            std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
+                                              fontSize, "", ttcIndex, builder->axes);
     minikin::Font::Builder fontBuilder(minikinFont);
 
     if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index fa1752c..a48d7f7 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -64,8 +64,8 @@
     sk_sp<SkImage> image = android::bitmap::toBitmap(bitmapHandle).makeImage();
     SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
-    sk_sp<SkImageFilter> bitmapFilter =
-        SkImageFilters::Image(image, srcRect, dstRect, kLow_SkFilterQuality);
+    sk_sp<SkImageFilter> bitmapFilter = SkImageFilters::Image(
+            image, srcRect, dstRect, SkSamplingOptions(SkFilterMode::kLinear));
     return reinterpret_cast<jlong>(bitmapFilter.release());
 }
 
@@ -150,4 +150,4 @@
     android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
             gRenderEffectMethods, NELEM(gRenderEffectMethods));
     return 0;
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 1dc5cd9..2e4d7f62 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -239,14 +239,12 @@
 
 static jlong RuntimeShader_createShaderBuilder(JNIEnv* env, jobject, jstring sksl) {
     ScopedUtfChars strSksl(env, sksl);
-    auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()));
-    sk_sp<SkRuntimeEffect> effect = std::get<0>(result);
-    if (effect.get() == nullptr) {
-        const auto& err = std::get<1>(result);
-        doThrowIAE(env, err.c_str());
+    auto result = SkRuntimeEffect::Make(SkString(strSksl.c_str()), SkRuntimeEffect::Options{});
+    if (result.effect.get() == nullptr) {
+        doThrowIAE(env, result.errorText.c_str());
         return 0;
     }
-    return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(effect)));
+    return reinterpret_cast<jlong>(new SkRuntimeShaderBuilder(std::move(result.effect)));
 }
 
 static void SkRuntimeShaderBuilder_delete(SkRuntimeShaderBuilder* builder) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 8f455fe..251323d 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -355,29 +355,48 @@
     env->SetStaticObjectField(cls, fid, typeface);
 }
 
+// Critical Native
+static jint Typeface_getFamilySize(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle) {
+    return toTypeface(faceHandle)->fFontCollection->getFamilies().size();
+}
+
+// Critical Native
+static jlong Typeface_getFamily(CRITICAL_JNI_PARAMS_COMMA jlong faceHandle, jint index) {
+    std::shared_ptr<minikin::FontFamily> family =
+            toTypeface(faceHandle)->fFontCollection->getFamilies()[index];
+    return reinterpret_cast<jlong>(new FontFamilyWrapper(std::move(family)));
+}
+
+// Regular JNI
+static void Typeface_warmUpCache(JNIEnv* env, jobject, jstring jFilePath) {
+    ScopedUtfChars filePath(env, jFilePath);
+    makeSkDataCached(filePath.c_str(), false /* fs verity */);
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gTypefaceMethods[] = {
-    { "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
-    { "nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
-            (void*)Typeface_createFromTypefaceWithExactStyle },
-    { "nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
-            (void*)Typeface_createFromTypefaceWithVariation },
-    { "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },
-    { "nativeGetReleaseFunc",     "()J",  (void*)Typeface_getReleaseFunc },
-    { "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },
-    { "nativeGetWeight",      "(J)I",  (void*)Typeface_getWeight },
-    { "nativeCreateFromArray",    "([JJII)J",
-                                           (void*)Typeface_createFromArray },
-    { "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
-    { "nativeGetSupportedAxes",   "(J)[I",  (void*)Typeface_getSupportedAxes },
-    { "nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
-          (void*)Typeface_registerGenericFamily },
-    { "nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
-    { "nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
-    { "nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
-          (void*)Typeface_forceSetStaticFinalField },
+        {"nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface},
+        {"nativeCreateFromTypefaceWithExactStyle", "(JIZ)J",
+         (void*)Typeface_createFromTypefaceWithExactStyle},
+        {"nativeCreateFromTypefaceWithVariation", "(JLjava/util/List;)J",
+         (void*)Typeface_createFromTypefaceWithVariation},
+        {"nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias},
+        {"nativeGetReleaseFunc", "()J", (void*)Typeface_getReleaseFunc},
+        {"nativeGetStyle", "(J)I", (void*)Typeface_getStyle},
+        {"nativeGetWeight", "(J)I", (void*)Typeface_getWeight},
+        {"nativeCreateFromArray", "([JJII)J", (void*)Typeface_createFromArray},
+        {"nativeSetDefault", "(J)V", (void*)Typeface_setDefault},
+        {"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
+        {"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
+         (void*)Typeface_registerGenericFamily},
+        {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
+        {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+        {"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
+         (void*)Typeface_forceSetStaticFinalField},
+        {"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
+        {"nativeGetFamily", "(JI)J", (void*)Typeface_getFamily},
+        {"nativeWarmUpCache", "(Ljava/lang/String;)V", (void*)Typeface_warmUpCache},
 };
 
 int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index a146b64..df66981 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -189,6 +189,13 @@
     }
 }
 
+static void android_view_ThreadedRenderer_setSurfaceControl(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jlong surfaceControlPtr) {
+    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+    ASurfaceControl* surfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControlPtr);
+    proxy->setSurfaceControl(surfaceControl);
+}
+
 static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
         jlong proxyPtr) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -603,14 +610,12 @@
 
 static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth,
                                                           jint physicalHeight, jfloat refreshRate,
-                                                          jfloat maxRefreshRate,
                                                           jint wideColorDataspace,
                                                           jlong appVsyncOffsetNanos,
                                                           jlong presentationDeadlineNanos) {
     DeviceInfo::setWidth(physicalWidth);
     DeviceInfo::setHeight(physicalHeight);
     DeviceInfo::setRefreshRate(refreshRate);
-    DeviceInfo::setMaxRefreshRate(maxRefreshRate);
     DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace));
     DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
     DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
@@ -673,6 +678,8 @@
         {"nSetName", "(JLjava/lang/String;)V", (void*)android_view_ThreadedRenderer_setName},
         {"nSetSurface", "(JLandroid/view/Surface;Z)V",
          (void*)android_view_ThreadedRenderer_setSurface},
+        {"nSetSurfaceControl", "(JJ)V",
+         (void*)android_view_ThreadedRenderer_setSurfaceControl},
         {"nPause", "(J)Z", (void*)android_view_ThreadedRenderer_pause},
         {"nSetStopped", "(JZ)V", (void*)android_view_ThreadedRenderer_setStopped},
         {"nSetLightAlpha", "(JFF)V", (void*)android_view_ThreadedRenderer_setLightAlpha},
@@ -735,7 +742,7 @@
         {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
         {"nSetDisplayDensityDpi", "(I)V",
          (void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
-        {"nInitDisplayInfo", "(IIFFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+        {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
         {"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
 };
 
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index c8471a9..5a972f5 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -137,12 +137,9 @@
     sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args);
 
     std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
-        std::move(newTypeface),
-        minikinSkia->GetFontData(),
-        minikinSkia->GetFontSize(),
-        minikinSkia->getFilePath(),
-        minikinSkia->GetFontIndex(),
-        builder->axes);
+            std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
+            minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
+            builder->axes);
     std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
               .setWeight(weight)
               .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -279,6 +276,12 @@
     return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
 }
 
+// Critical Native
+static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
+    FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
+    return font->font->typeface()->GetSourceId();
+}
+
 // Fast Native
 static jlong FontFileUtil_getFontRevision(JNIEnv* env, jobject, jobject buffer, jint index) {
     NPE_CHECK_RETURN_ZERO(env, buffer);
@@ -369,6 +372,7 @@
         {"nGetIndex", "(J)I", (void*)Font_getIndex},
         {"nGetAxisCount", "(J)I", (void*)Font_getAxisCount},
         {"nGetAxisInfo", "(JI)J", (void*)Font_getAxisInfo},
+        {"nGetSourceId", "(J)I", (void*)Font_getSourceId},
 };
 
 static const JNINativeMethod gFontFileUtilMethods[] = {
@@ -409,10 +413,15 @@
     if (face == nullptr) {
         return nullptr;
     }
-    return std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize,
+    return std::make_shared<MinikinFontSkia>(std::move(face), getNewSourceId(), fontPtr, fontSize,
                                              fontPath, ttcIndex, axes);
 }
 
+int getNewSourceId() {
+    static std::atomic<int> sSourceId = {0};
+    return sSourceId++;
+}
+
 }  // namespace fonts
 
 }  // namespace android
diff --git a/libs/hwui/jni/fonts/Font.h b/libs/hwui/jni/fonts/Font.h
index b5d20bf..4bf60ee 100644
--- a/libs/hwui/jni/fonts/Font.h
+++ b/libs/hwui/jni/fonts/Font.h
@@ -33,6 +33,8 @@
         sk_sp<SkData>&& data, std::string_view fontPath, const void *fontPtr, size_t fontSize,
         int ttcIndex, const std::vector<minikin::FontVariation>& axes);
 
+int getNewSourceId();
+
 } // namespace fonts
 
 } // namespace android
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 37a6ee7..9543d47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -108,7 +108,6 @@
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
-    setRenderAheadDepth(Properties::defaultRenderAhead);
 }
 
 CanvasContext::~CanvasContext() {
@@ -134,6 +133,7 @@
 void CanvasContext::destroy() {
     stopDrawing();
     setSurface(nullptr);
+    setSurfaceControl(nullptr);
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
@@ -157,30 +157,36 @@
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
-    if (mFixedRenderAhead) {
-        mRenderAheadCapacity = mRenderAheadDepth;
-    } else {
-        if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
-            mRenderAheadCapacity = 1;
-        } else {
-            mRenderAheadCapacity = 0;
-        }
-    }
-
     if (window) {
+        int extraBuffers = 0;
+        native_window_get_extra_buffer_count(window, &extraBuffers);
+
         mNativeSurface = std::make_unique<ReliableSurface>(window);
         mNativeSurface->init();
         if (enableTimeout) {
             // TODO: Fix error handling & re-shorten timeout
             ANativeWindow_setDequeueTimeout(window, 4000_ms);
         }
-        mNativeSurface->setExtraBufferCount(mRenderAheadCapacity);
+        mNativeSurface->setExtraBufferCount(extraBuffers);
     } else {
         mNativeSurface = nullptr;
     }
     setupPipelineSurface();
 }
 
+void CanvasContext::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    if (surfaceControl == mSurfaceControl) return;
+
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (mSurfaceControl != nullptr) {
+        funcs.releaseFunc(mSurfaceControl);
+    }
+    mSurfaceControl = surfaceControl;
+    if (mSurfaceControl != nullptr) {
+        funcs.acquireFunc(mSurfaceControl);
+    }
+}
+
 void CanvasContext::setupPipelineSurface() {
     bool hasSurface = mRenderPipeline->setSurface(
             mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior);
@@ -441,24 +447,6 @@
     mRenderThread.pushBackFrameCallback(this);
 }
 
-void CanvasContext::setPresentTime() {
-    int64_t presentTime = NATIVE_WINDOW_TIMESTAMP_AUTO;
-    int renderAhead = 0;
-    const auto frameIntervalNanos = mRenderThread.timeLord().frameIntervalNanos();
-    if (mFixedRenderAhead) {
-        renderAhead = std::min(mRenderAheadDepth, mRenderAheadCapacity);
-    } else if (frameIntervalNanos < 15_ms) {
-        renderAhead = std::min(1, static_cast<int>(mRenderAheadCapacity));
-    }
-
-    if (renderAhead) {
-        presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
-                (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() +
-                (frameIntervalNanos / 2);
-    }
-    native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime);
-}
-
 void CanvasContext::draw() {
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
@@ -478,8 +466,6 @@
     mCurrentFrameInfo->markIssueDrawCommandsStart();
 
     Frame frame = mRenderPipeline->getFrame();
-    setPresentTime();
-
     SkRect windowDirty = computeDirtyRect(frame, &dirty);
 
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
@@ -765,19 +751,6 @@
     return width != mLastFrameWidth || height != mLastFrameHeight;
 }
 
-void CanvasContext::setRenderAheadDepth(int renderAhead) {
-    if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) {
-        return;
-    }
-    if (renderAhead == -1) {
-        mFixedRenderAhead = false;
-        mRenderAheadDepth = 0;
-    } else {
-        mFixedRenderAhead = true;
-        mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
-    }
-}
-
 SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
     if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
         // can't rely on prior content of window if viewport size changes
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index cc4eb32..917b00c 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -112,6 +112,7 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
     bool hasSurface() const { return mNativeSurface.get(); }
@@ -193,9 +194,6 @@
         return mUseForceDark;
     }
 
-    // Must be called before setSurface
-    void setRenderAheadDepth(int renderAhead);
-
     SkISize getNextFrameSize() const;
 
 private:
@@ -211,7 +209,6 @@
 
     bool isSwapChainStuffed();
     bool surfaceRequiresRedraw();
-    void setPresentTime();
     void setupPipelineSurface();
 
     SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
@@ -222,6 +219,9 @@
 
     RenderThread& mRenderThread;
     std::unique_ptr<ReliableSurface> mNativeSurface;
+    // The SurfaceControl reference is passed from ViewRootImpl, can be set to
+    // NULL to remove the reference
+    ASurfaceControl* mSurfaceControl = nullptr;
     // stopped indicates the CanvasContext will reject actual redraw operations,
     // and defer repaint until it is un-stopped
     bool mStopped = false;
@@ -232,9 +232,6 @@
     // painted onto its surface.
     bool mIsDirty = false;
     SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default;
-    bool mFixedRenderAhead = false;
-    uint32_t mRenderAheadDepth = 0;
-    uint32_t mRenderAheadCapacity = 0;
     struct SwapHistory {
         SkRect damage;
         nsecs_t vsyncTime;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b51f6dc..e14842f 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -84,6 +84,19 @@
     });
 }
 
+void RenderProxy::setSurfaceControl(ASurfaceControl* surfaceControl) {
+    auto funcs = mRenderThread.getASurfaceControlFunctions();
+    if (surfaceControl) {
+        funcs.acquireFunc(surfaceControl);
+    }
+    mRenderThread.queue().post([this, control = surfaceControl, funcs]() mutable {
+        mContext->setSurfaceControl(control);
+        if (control) {
+            funcs.releaseFunc(control);
+        }
+    });
+}
+
 void RenderProxy::allocateBuffers() {
     mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
 }
@@ -295,11 +308,6 @@
     mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
 }
 
-void RenderProxy::setRenderAheadDepth(int renderAhead) {
-    mRenderThread.queue().post(
-            [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
-}
-
 int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
                                  SkBitmap* bitmap) {
     auto& thread = RenderThread::getInstance();
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 33dabc9..366d6b5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -20,6 +20,7 @@
 #include <SkBitmap.h>
 #include <android/native_window.h>
 #include <cutils/compiler.h>
+#include <android/surface_control.h>
 #include <utils/Functor.h>
 
 #include "../FrameMetricsObserver.h"
@@ -72,6 +73,7 @@
     void setName(const char* name);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
+    void setSurfaceControl(ASurfaceControl* surfaceControl);
     void allocateBuffers();
     bool pause();
     void setStopped(bool stopped);
@@ -123,23 +125,6 @@
     void removeFrameMetricsObserver(FrameMetricsObserver* observer);
     void setForceDark(bool enable);
 
-    /**
-     * Sets a render-ahead depth on the backing renderer. This will increase latency by
-     * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>.
-     * In return the renderer will be less susceptible to jitter, resulting in a smoother animation.
-     *
-     * Not recommended to use in response to anything touch driven, but for canned animations
-     * where latency is not a concern careful use may be beneficial.
-     *
-     * Note that when increasing this there will be a frame gap of N frames where N is
-     * renderAhead - <current renderAhead>. When decreasing this if there are any pending
-     * frames they will retain their prior renderAhead value, so it will take a few frames
-     * for the decrease to flush through.
-     *
-     * @param renderAhead How far to render ahead, must be in the range [0..2]
-     */
-    void setRenderAheadDepth(int renderAhead);
-
     static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
                                            int bottom, SkBitmap* bitmap);
     static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7750a31..2610186 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -33,6 +33,7 @@
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
+#include <dlfcn.h>
 #include <sys/resource.h>
 #include <utils/Condition.h>
 #include <utils/Log.h>
@@ -49,6 +50,17 @@
 
 static JVMAttachHook gOnStartHook = nullptr;
 
+ASurfaceControlFunctions::ASurfaceControlFunctions() {
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    acquireFunc = (ASC_acquire) dlsym(handle_, "ASurfaceControl_acquire");
+    LOG_ALWAYS_FATAL_IF(acquireFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_acquire!");
+
+    releaseFunc = (ASC_release) dlsym(handle_, "ASurfaceControl_release");
+    LOG_ALWAYS_FATAL_IF(releaseFunc == nullptr,
+            "Failed to find required symbol ASurfaceControl_release!");
+}
+
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
     int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4fbb0716..bb7c518 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -78,6 +78,16 @@
     virtual ~VsyncSource() {}
 };
 
+typedef void (*ASC_acquire)(ASurfaceControl* control);
+typedef void (*ASC_release)(ASurfaceControl* control);
+
+struct ASurfaceControlFunctions {
+    ASurfaceControlFunctions();
+
+    ASC_acquire acquireFunc;
+    ASC_release releaseFunc;
+};
+
 class ChoreographerSource;
 class DummyVsyncSource;
 
@@ -121,6 +131,10 @@
 
     void preload();
 
+    const ASurfaceControlFunctions& getASurfaceControlFunctions() {
+        return mASurfaceControlFunctions;
+    }
+
     /**
      * isCurrent provides a way to query, if the caller is running on
      * the render thread.
@@ -189,6 +203,8 @@
     sk_sp<GrDirectContext> mGrContext;
     CacheManager* mCacheManager;
     sp<VulkanManager> mVkManager;
+
+    ASurfaceControlFunctions mASurfaceControlFunctions;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 74a039b..91022cf 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -38,7 +38,6 @@
         int count = 0;
         int reportFrametimeWeight = 0;
         bool renderOffscreen = true;
-        int renderAhead = 0;
     };
 
     template <class T>
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index eda5d22..8c7d261 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -153,11 +153,6 @@
     proxy->resetProfileInfo();
     proxy->fence();
 
-    if (opts.renderAhead) {
-        usleep(33000);
-    }
-    proxy->setRenderAheadDepth(opts.renderAhead);
-
     ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);
 
     nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 88d33c3..174a140 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -69,7 +69,6 @@
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
-  --render-ahead=NUM   Sets how far to render-ahead. Must be 0 (default), 1, or 2.
 )");
 }
 
@@ -171,7 +170,6 @@
     Onscreen,
     Offscreen,
     Renderer,
-    RenderAhead,
 };
 }
 
@@ -187,7 +185,6 @@
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
-        {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead},
         {0, 0, 0, 0}};
 
 static const char* SHORT_OPTIONS = "c:r:h";
@@ -286,16 +283,6 @@
                 gOpts.renderOffscreen = true;
                 break;
 
-            case LongOpts::RenderAhead:
-                if (!optarg) {
-                    error = true;
-                }
-                gOpts.renderAhead = atoi(optarg);
-                if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) {
-                    error = true;
-                }
-                break;
-
             case 'h':
                 printHelp();
                 exit(EXIT_SUCCESS);
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 5d2aa2f..ab23448 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
     sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font =
-            std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0,
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
                                               std::vector<minikin::FontVariation>());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index d291ec0..438a92e 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -95,7 +95,7 @@
     name: "libincident_test",
     test_config: "AndroidTest.xml",
     defaults: ["libincidentpriv_defaults"],
-    test_suites: ["device-tests", "mts"],
+    test_suites: ["device-tests", "mts-statsd"],
     compile_multilib: "both",
     multilib: {
         lib64: {
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index a7e9a0d..5e2e559 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -569,7 +569,12 @@
 
     /** @hide */
     public long getElapsedRealtimeAgeMillis() {
-        return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+        return getElapsedRealtimeAgeMillis(SystemClock.elapsedRealtime());
+    }
+
+    /** @hide */
+    public long getElapsedRealtimeAgeMillis(long referenceRealtimeMs) {
+        return referenceRealtimeMs - NANOSECONDS.toMillis(mElapsedRealtimeNanos);
     }
 
     /**
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index cf2f0f0..a7ed091 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -10,7 +10,7 @@
           "include-filter": "com.google.android.media.gts.WidevineGenericOpsTests"
         },
         {
-          "include-filter": "com.google.android.media.gts.WidevineYouTubePerformanceTests"
+          "include-filter": "com.google.android.media.gts.WidevineH264PlaybackTests"
         }
       ]
     }
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 27f72687..ede1dbf 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -181,6 +181,21 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerState {}
 
+    /** @hide */
+    public static String playerStateToString(@PlayerState int state) {
+        switch (state) {
+            case PLAYER_STATE_UNKNOWN: return "PLAYER_STATE_UNKNOWN";
+            case PLAYER_STATE_RELEASED: return "PLAYER_STATE_RELEASED";
+            case PLAYER_STATE_IDLE: return "PLAYER_STATE_IDLE";
+            case PLAYER_STATE_STARTED: return "PLAYER_STATE_STARTED";
+            case PLAYER_STATE_PAUSED: return "PLAYER_STATE_PAUSED";
+            case PLAYER_STATE_STOPPED: return "PLAYER_STATE_STOPPED";
+            case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
+            default:
+                return "invalid state " + state;
+        }
+    }
+
     // immutable data
     private final int mPlayerIId;
 
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e056d43..7fb83f1 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -2726,8 +2726,10 @@
             }
         }
         synchronized(mPlayStateLock) {
+            baseStart(0); // unknown device at this point
             native_start();
-            baseStart(native_getRoutedDeviceId());
+            // FIXME see b/179218630
+            //baseStart(native_getRoutedDeviceId());
             if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
                 mPlayState = PLAYSTATE_STOPPING;
             } else {
diff --git a/media/java/android/media/DrmInitData.java b/media/java/android/media/DrmInitData.java
index 85b4ba5..3c48f8f 100644
--- a/media/java/android/media/DrmInitData.java
+++ b/media/java/android/media/DrmInitData.java
@@ -19,6 +19,7 @@
 import android.media.MediaDrm;
 
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -94,9 +95,9 @@
          * @param data The initialization data.
          */
         public SchemeInitData(@NonNull UUID uuid, @NonNull String mimeType, @NonNull byte[] data) {
-            this.uuid = uuid;
-            this.mimeType = mimeType;
-            this.data = data;
+            this.uuid = Objects.requireNonNull(uuid);
+            this.mimeType = Objects.requireNonNull(mimeType);
+            this.data = Objects.requireNonNull(data);
         }
 
         @Override
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 02fa94c..5a7ff7f 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -33,7 +33,8 @@
         float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void stop(IBinder token);
     boolean isPlaying(IBinder token);
-    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping);
+    oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
+        boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
     oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa);
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 92db946..44f8385 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -23,6 +23,7 @@
 import android.graphics.ImageFormat.Format;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.hardware.HardwareBuffer;
 import android.os.Handler;
@@ -202,6 +203,25 @@
         if (format == ImageFormat.UNKNOWN) {
             format = SurfaceUtils.getSurfaceFormat(surface);
         }
+        // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native
+        // allocation estimation sequence depends on the public formats values. To avoid
+        // possible errors, convert where necessary.
+        if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) {
+            int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface);
+            switch (surfaceDataspace) {
+                case StreamConfigurationMap.HAL_DATASPACE_DEPTH:
+                    format = ImageFormat.DEPTH_POINT_CLOUD;
+                    break;
+                case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH:
+                    format = ImageFormat.DEPTH_JPEG;
+                    break;
+                case StreamConfigurationMap.HAL_DATASPACE_HEIF:
+                    format = ImageFormat.HEIC;
+                    break;
+                default:
+                    format = ImageFormat.JPEG;
+            }
+        }
         // Estimate the native buffer allocation size and register it so it gets accounted for
         // during GC. Note that this doesn't include the buffers required by the buffer queue
         // itself and the buffers requested by the producer.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 1a49b85..67f1660 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1154,6 +1153,22 @@
     public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
 
     /**
+     * An optional key describing the opto-electronic transfer function
+     * requested for the output video content.
+     *
+     * The associated value is an integer: 0 if unspecified, or one of the
+     * COLOR_TRANSFER_ values. When unspecified the component will not touch the
+     * video content; otherwise the component will tone-map the raw video frame
+     * to match the requested transfer function.
+     *
+     * After configure, component's input format will contain this key to note
+     * whether the request is supported or not. If the value in the input format
+     * is the same as the requested value, the request is supported. The value
+     * is set to 0 if unsupported.
+     */
+    public static final String KEY_COLOR_TRANSFER_REQUEST = "color-transfer-request";
+
+    /**
      * A key describing a unique ID for the content of a media track.
      *
      * <p>This key is used by {@link MediaExtractor}. Some extractors provide multiple encodings
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index ca0d29f..2c45ed3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1356,6 +1356,7 @@
     }
 
     private void startImpl() {
+        baseStart(0); // unknown device at this point
         stayAwake(true);
         _start();
     }
@@ -1381,6 +1382,7 @@
     public void stop() throws IllegalStateException {
         stayAwake(false);
         _stop();
+        baseStop();
     }
 
     private native void _stop() throws IllegalStateException;
@@ -1394,6 +1396,7 @@
     public void pause() throws IllegalStateException {
         stayAwake(false);
         _pause();
+        basePause();
     }
 
     private native void _pause() throws IllegalStateException;
@@ -3479,7 +3482,8 @@
             case MEDIA_STOPPED:
                 {
                     tryToDisableNativeRoutingCallback();
-                    baseStop();
+                    // FIXME see b/179218630
+                    //baseStop();
                     TimeProvider timeProvider = mTimeProvider;
                     if (timeProvider != null) {
                         timeProvider.onStopped();
@@ -3489,15 +3493,17 @@
 
             case MEDIA_STARTED:
                 {
-                    baseStart(native_getRoutedDeviceId());
+                    // FIXME see b/179218630
+                    //baseStart(native_getRoutedDeviceId());
                     tryToEnableNativeRoutingCallback();
                 }
                 // fall through
             case MEDIA_PAUSED:
                 {
-                    if (msg.what == MEDIA_PAUSED) {
-                        basePause();
-                    }
+                    // FIXME see b/179218630
+                    //if (msg.what == MEDIA_PAUSED) {
+                    //    basePause();
+                    //}
                     TimeProvider timeProvider = mTimeProvider;
                     if (timeProvider != null) {
                         timeProvider.onPaused(msg.what == MEDIA_PAUSED);
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index bd783ce..79d505e 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -24,6 +24,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
+import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -77,6 +78,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private MediaPlayer mLocalPlayer;
     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+    private HapticGenerator mHapticGenerator;
 
     @UnsupportedAppUsage
     private Uri mUri;
@@ -89,6 +91,7 @@
     // playback properties, use synchronized with mPlaybackSettingsLock
     private boolean mIsLooping = false;
     private float mVolume = 1.0f;
+    private boolean mHapticGeneratorEnabled = false;
     private final Object mPlaybackSettingsLock = new Object();
 
     /** {@hide} */
@@ -197,15 +200,50 @@
     }
 
     /**
+     * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
+     * only be enabled on devices that support the effect.
+     *
+     * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
+     * @see android.media.audiofx.HapticGenerator#isAvailable()
+     */
+    public boolean setHapticGeneratorEnabled(boolean enabled) {
+        if (!HapticGenerator.isAvailable()) {
+            return false;
+        }
+        synchronized (mPlaybackSettingsLock) {
+            mHapticGeneratorEnabled = enabled;
+            applyPlaybackProperties_sync();
+        }
+        return true;
+    }
+
+    /**
+     * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
+     * @return true if the HapticGenerator is enabled.
+     */
+    public boolean isHapticGeneratorEnabled() {
+        synchronized (mPlaybackSettingsLock) {
+            return mHapticGeneratorEnabled;
+        }
+    }
+
+    /**
      * Must be called synchronized on mPlaybackSettingsLock
      */
     private void applyPlaybackProperties_sync() {
         if (mLocalPlayer != null) {
             mLocalPlayer.setVolume(mVolume);
             mLocalPlayer.setLooping(mIsLooping);
+            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+            }
+            if (mHapticGenerator != null) {
+                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+            }
         } else if (mAllowRemote && (mRemotePlayer != null)) {
             try {
-                mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
+                mRemotePlayer.setPlaybackProperties(
+                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
             } catch (RemoteException e) {
                 Log.w(TAG, "Problem setting playback properties: ", e);
             }
@@ -413,6 +451,10 @@
 
     private void destroyLocalPlayer() {
         if (mLocalPlayer != null) {
+            if (mHapticGenerator != null) {
+                mHapticGenerator.release();
+                mHapticGenerator = null;
+            }
             mLocalPlayer.setOnCompletionListener(null);
             mLocalPlayer.reset();
             mLocalPlayer.release();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98b9ad8..740bc2d 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2526,6 +2526,7 @@
          * Pauses TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#pauseRecording(Bundle)}.
          */
         void pauseRecording(@NonNull Bundle params) {
             if (mToken == null) {
@@ -2543,6 +2544,7 @@
          * Resumes TV program recording in the current recording session.
          *
          * @param params A set of extra parameters which might be handled with this event.
+         *        {@link TvRecordingClient#resumeRecording(Bundle)}.
          */
         void resumeRecording(@NonNull Bundle params) {
             if (mToken == null) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b64d7..4972529 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -180,6 +180,10 @@
         "libstagefright_foundation_headers",
     ],
 
+    // TunerService is a system service required for Tuner feature.
+    // TunerJNI is a client of TunerService so we build the dependency here.
+    required: ["mediatuner"],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/tuner/ClientHelper.h b/media/jni/tuner/ClientHelper.h
index 185b2f6..508dccf 100644
--- a/media/jni/tuner/ClientHelper.h
+++ b/media/jni/tuner/ClientHelper.h
@@ -19,6 +19,7 @@
 
 #include <android/binder_parcel_utils.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
+#include <utils/Log.h>
 
 using Status = ::ndk::ScopedAStatus;
 
@@ -37,6 +38,7 @@
         } else if (s.isOk()) {
             return Result::SUCCESS;
         }
+        ALOGE("Aidl exception code %s", s.getDescription().c_str());
         return Result::UNKNOWN_ERROR;
     }
 };
diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp
index 748d458..359ef36 100644
--- a/media/jni/tuner/DemuxClient.cpp
+++ b/media/jni/tuner/DemuxClient.cpp
@@ -216,14 +216,13 @@
 Result DemuxClient::close() {
     if (mTunerDemux != NULL) {
         Status s = mTunerDemux->close();
+        mDemux = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDemux != NULL) {
         Result res = mDemux->close();
-        if (res == Result::SUCCESS) {
-            mDemux = NULL;
-        }
+        mDemux = NULL;
         return res;
     }
 
diff --git a/media/jni/tuner/DescramblerClient.cpp b/media/jni/tuner/DescramblerClient.cpp
index c9bacda..07be5cf 100644
--- a/media/jni/tuner/DescramblerClient.cpp
+++ b/media/jni/tuner/DescramblerClient.cpp
@@ -101,14 +101,18 @@
 Result DescramblerClient::close() {
     if (mTunerDescrambler != NULL) {
         Status s = mTunerDescrambler->close();
+        mTunerDescrambler = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDescrambler != NULL) {
-        return mDescrambler->close();
+        Result res = mDescrambler->close();
+        mDescrambler = NULL;
+        return res;
     }
 
-    return Result::INVALID_STATE;}
+    return Result::INVALID_STATE;
+}
 
 /////////////// DescramblerClient Helper Methods ///////////////////////
 
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 0400485..7793180 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -316,14 +316,13 @@
 Result DvrClient::close() {
     if (mTunerDvr != NULL) {
         Status s = mTunerDvr->close();
+        mTunerDvr = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mDvr != NULL) {
         Result res = mDvr->close();
-        if (res == Result::SUCCESS) {
-            mDvr = NULL;
-        }
+        mDvr = NULL;
         return res;
     }
 
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index 8b4ca37..f31d465 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -43,6 +43,7 @@
 using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterMonitorEvent;
 using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
 
 namespace android {
@@ -261,14 +262,14 @@
     if (mTunerFilter != NULL) {
         Status s = mTunerFilter->close();
         closeAvSharedMemory();
+        mTunerFilter = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mFilter != NULL) {
         Result res = mFilter->close();
-        if (res == Result::SUCCESS) {
-            mFilter = NULL;
-        }
+        mFilter = NULL;
+        mFilter_1_1 = NULL;
         closeAvSharedMemory();
         return res;
     }
@@ -480,7 +481,7 @@
         case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
             int size = ipAddr.srcIpAddress.v4().size();
             srcIpAddress.isIpV6 = false;
-            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            srcIpAddress.addr.resize(size);
             copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
                     srcIpAddress.addr.begin());
             break;
@@ -493,8 +494,6 @@
                     srcIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
     switch (ipAddr.dstIpAddress.getDiscriminator()) {
         case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
@@ -513,8 +512,6 @@
                     dstIpAddress.addr.begin());
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -696,8 +693,6 @@
             getHidlRestartEvent(filterEvents, eventExt);
             break;
         }
-        default:
-            break;
     }
 }
 
@@ -883,19 +878,18 @@
         DemuxFilterEventExt& eventExt) {
     auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
     eventExt.events.resize(1);
+    DemuxFilterMonitorEvent monitorEvent;
     switch (monitor.getTag()) {
         case TunerFilterMonitorEvent::scramblingStatus: {
-            eventExt.events[0].monitorEvent().scramblingStatus(
-                    static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            monitorEvent.scramblingStatus(static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
         case TunerFilterMonitorEvent::cid: {
-            eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+            monitorEvent.cid(static_cast<uint32_t>(monitor.cid));
+            eventExt.events[0].monitorEvent(monitorEvent);
             break;
         }
-        default:
-            eventExt.events[0].noinit();
-            break;
     }
 }
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index 3a00133..9e36642 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -49,9 +49,12 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
@@ -61,19 +64,22 @@
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendGuardInterval;
+using ::android::hardware::tv::tuner::V1_1::FrontendInterleaveMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendRollOff;
 using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendTransmissionMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendType;
 
 namespace android {
 
 /////////////// FrontendClient ///////////////////////
 
-FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type) {
+FrontendClient::FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type) {
     mTunerFrontend = tunerFrontend;
     mAidlCallback = NULL;
     mHidlCallback = NULL;
-    mId = id;
     mType = type;
 }
 
@@ -104,6 +110,11 @@
     mFrontend_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFrontend);
 }
 
+// TODO: move after migration is done
+void FrontendClient::setId(int id) {
+    mId = id;
+}
+
 Result FrontendClient::tune(const FrontendSettings& settings,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
@@ -311,15 +322,14 @@
 Result FrontendClient::close() {
     if (mTunerFrontend != NULL) {
         Status s = mTunerFrontend->close();
+        mTunerFrontend = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mFrontend != NULL) {
         Result result = mFrontend->close();
-        if (result == Result::SUCCESS) {
-            mFrontend = NULL;
-            mFrontend_1_1 = NULL;
-        }
+        mFrontend = NULL;
+        mFrontend_1_1 = NULL;
         return result;
     }
 
@@ -333,13 +343,26 @@
 }
 
 int FrontendClient::getId() {
-    return mId;
+    if (mTunerFrontend != NULL) {
+        Status s = mTunerFrontend->getFrontendId(&mId);
+        if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+            return mId;
+        }
+        ALOGE("Failed to getFrontendId from Tuner Frontend");
+        return -1;
+    }
+
+    if (mFrontend != NULL) {
+        return mId;
+    }
+
+    return -1;
 }
 
 vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
     vector<FrontendStatus> hidlStatus;
     for (TunerFrontendStatus s : aidlStatus) {
-        FrontendStatus status;
+        FrontendStatus status = FrontendStatus();
         switch (s.getTag()) {
             case TunerFrontendStatus::isDemodLocked: {
                 status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
@@ -389,25 +412,31 @@
             }
             case TunerFrontendStatus::modulation: {
                 auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+                FrontendModulationStatus modulation;
                 switch (mType) {
                     case (int)FrontendType::DVBC:
-                        status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        modulation.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBS:
-                        status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        modulation.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        modulation.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        modulation.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        modulation.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        status.modulation(modulation);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -466,7 +495,7 @@
             }
             case TunerFrontendStatus::hierarchy: {
                 status.hierarchy(static_cast<FrontendDvbtHierarchy>(
-                        s.get<TunerFrontendStatus::freqOffset>()));
+                        s.get<TunerFrontendStatus::hierarchy>()));
                 hidlStatus.push_back(status);
                 break;
             }
@@ -477,15 +506,16 @@
             }
             case TunerFrontendStatus::plpInfo: {
                 int size = s.get<TunerFrontendStatus::plpInfo>().size();
-                status.plpInfo().resize(size);
+                hidl_vec<FrontendStatusAtsc3PlpInfo> info(size);
                 for (int i = 0; i < size; i++) {
                     auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
-                    status.plpInfo()[i] = {
+                    info[i] = {
                         .plpId = (uint8_t)aidlInfo.plpId,
                         .isLocked = aidlInfo.isLocked,
                         .uec = (uint32_t)aidlInfo.uec,
                     };
                 }
+                status.plpInfo(info);
                 hidlStatus.push_back(status);
                 break;
             }
@@ -503,52 +533,54 @@
         FrontendStatusExt1_1 status;
         switch (s.getTag()) {
             case TunerFrontendStatus::modulations: {
+                vector<FrontendModulation> ms;
                 for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
-                    int size = status.modulations().size();
-                    status.modulations().resize(size + 1);
+                    FrontendModulation m;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.modulations()[size].dvbc(
-                                    static_cast<FrontendDvbcModulation>(aidlMod));
+                            m.dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBS:
-                            status.modulations()[size].dvbs(
-                                    static_cast<FrontendDvbsModulation>(aidlMod));
+                            m.dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DVBT:
-                            status.modulations()[size].dvbt(
-                                    static_cast<FrontendDvbtConstellation>(aidlMod));
+                            m.dvbt(static_cast<FrontendDvbtConstellation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS:
-                            status.modulations()[size].isdbs(
-                                    static_cast<FrontendIsdbsModulation>(aidlMod));
+                            m.isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBS3:
-                            status.modulations()[size].isdbs3(
-                                    static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            m.isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ISDBT:
-                            status.modulations()[size].isdbt(
-                                    static_cast<FrontendIsdbtModulation>(aidlMod));
+                            m.isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC:
-                            status.modulations()[size].atsc(
-                                    static_cast<FrontendAtscModulation>(aidlMod));
+                            m.atsc(static_cast<FrontendAtscModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.modulations()[size].atsc3(
-                                    static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            m.atsc3(static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.modulations()[size].dtmb(
-                                    static_cast<FrontendDtmbModulation>(aidlMod));
+                            m.dtmb(static_cast<FrontendDtmbModulation>(aidlMod));
+                            ms.push_back(m);
                             break;
                         default:
-                            status.modulations().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (ms.size() > 0) {
+                    status.modulations(ms);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::bers: {
@@ -571,25 +603,31 @@
             }
             case TunerFrontendStatus::bandwidth: {
                 auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+                FrontendBandwidth band;
                 switch (mType) {
                     case (int)FrontendType::ATSC3:
-                        status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        band.atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBC:
-                        status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        band.dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DVBT:
-                        status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        band.dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        band.isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        band.dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        status.bandwidth(band);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -599,17 +637,21 @@
             }
             case TunerFrontendStatus::interval: {
                 auto aidlInter = s.get<TunerFrontendStatus::interval>();
+                FrontendGuardInterval inter;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        inter.dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        inter.isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        inter.dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        status.interval(inter);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -619,19 +661,21 @@
             }
             case TunerFrontendStatus::transmissionMode: {
                 auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+                FrontendTransmissionMode trans;
                 switch (mType) {
                     case (int)FrontendType::DVBT:
-                        status.transmissionMode().dvbt(
-                                static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        trans.dvbt(static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBT:
-                        status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        trans.isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::DTMB:
-                        status.transmissionMode().dtmb(
-                                static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        trans.dtmb(static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        status.transmissionMode(trans);
                         hidlStatus.push_back(status);
                         break;
                     default:
@@ -650,28 +694,30 @@
                 break;
             }
             case TunerFrontendStatus::interleaving: {
+                vector<FrontendInterleaveMode> modes;
                 for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
-                    int size = status.interleaving().size();
-                    status.interleaving().resize(size + 1);
+                    FrontendInterleaveMode mode;
                     switch (mType) {
                         case (int)FrontendType::DVBC:
-                            status.interleaving()[size].dvbc(
-                                    static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            mode.dvbc(static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::ATSC3:
-                            status.interleaving()[size].atsc3(
-                                    static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            mode.atsc3(static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         case (int)FrontendType::DTMB:
-                            status.interleaving()[size].dtmb(
-                                    static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            mode.dtmb(static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            modes.push_back(mode);
                             break;
                         default:
-                            status.interleaving().resize(size);
                             break;
                     }
                 }
-                hidlStatus.push_back(status);
+                if (modes.size() > 0) {
+                    status.interleaving(modes);
+                    hidlStatus.push_back(status);
+                }
                 break;
             }
             case TunerFrontendStatus::isdbtSegment: {
@@ -690,17 +736,21 @@
             }
             case TunerFrontendStatus::rollOff: {
                 auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+                FrontendRollOff roll;
                 switch (mType) {
                     case (int)FrontendType::DVBS:
-                        status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        roll.dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS:
-                        status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        roll.isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     case (int)FrontendType::ISDBS3:
-                        status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        roll.isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        status.rollOff(roll);
                         hidlStatus.push_back(status);
                         break;
                     default:
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 298b397..f71616c 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -108,7 +108,7 @@
 struct FrontendClient : public RefBase {
 
 public:
-    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int id, int type);
+    FrontendClient(shared_ptr<ITunerFrontend> tunerFrontend, int type);
     ~FrontendClient();
 
     /**
@@ -180,6 +180,7 @@
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
 
+    void setId(int id);
     int getId();
 
 private:
diff --git a/media/jni/tuner/LnbClient.cpp b/media/jni/tuner/LnbClient.cpp
index 8d08c25..5b6e46e 100644
--- a/media/jni/tuner/LnbClient.cpp
+++ b/media/jni/tuner/LnbClient.cpp
@@ -113,11 +113,14 @@
 Result LnbClient::close() {
     if (mTunerLnb != NULL) {
         Status s = mTunerLnb->close();
+        mTunerLnb = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mLnb != NULL) {
-        return mLnb->close();
+        Result res = mLnb->close();
+        mLnb = NULL;
+        return res;
     }
 
     return Result::INVALID_STATE;
diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp
index 432238d..e123c9f 100644
--- a/media/jni/tuner/TimeFilterClient.cpp
+++ b/media/jni/tuner/TimeFilterClient.cpp
@@ -126,11 +126,14 @@
 Result TimeFilterClient::close() {
     if (mTunerTimeFilter != NULL) {
         Status s = mTunerTimeFilter->close();
+        mTunerTimeFilter = NULL;
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     if (mTimeFilter != NULL) {
-        return mTimeFilter->close();
+        Result res = mTimeFilter->close();
+        mTimeFilter = NULL;
+        return res;
     }
 
     return Result::INVALID_STATE;
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 7f954b5..cf17ed6 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -46,13 +46,12 @@
     // Connect with Tuner Service.
     ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
     mTunerService = ITunerService::fromBinder(binder);
-    // TODO: Remove after JNI migration is done.
-    mTunerService = NULL;
     if (mTunerService == NULL) {
         ALOGE("Failed to get tuner service");
     } else {
         // TODO: b/178124017 update TRM in TunerService independently.
         mTunerService->updateTunerResources();
+        mTunerService->getTunerHalVersion(&mTunerVersion);
     }
 }
 
@@ -115,7 +114,7 @@
         if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
             return NULL;
         }
-        return new FrontendClient(tunerFrontend, frontendHandle, aidlFrontendInfo.type);
+        return new FrontendClient(tunerFrontend, aidlFrontendInfo.type);
     }
 
     if (mTuner != NULL) {
@@ -127,8 +126,10 @@
             if (res != Result::SUCCESS) {
                 return NULL;
             }
-            sp<FrontendClient> frontendClient = new FrontendClient(NULL, id, (int)hidlInfo.type);
+            sp<FrontendClient> frontendClient = new FrontendClient(
+                    NULL, (int)hidlInfo.type);
             frontendClient->setHidlFrontend(hidlFrontend);
+            frontendClient->setId(id);
             return frontendClient;
         }
     }
@@ -358,7 +359,7 @@
 
 sp<ITuner> TunerClient::getHidlTuner() {
     if (mTuner == NULL) {
-        mTunerVersion = 0;
+        mTunerVersion = TUNER_HAL_VERSION_UNKNOWN;
         mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
 
         if (mTuner_1_1 == NULL) {
@@ -367,11 +368,11 @@
             if (mTuner == NULL) {
                 ALOGW("Failed to get tuner 1.0 service.");
             } else {
-                mTunerVersion = 1 << 16;
+                mTunerVersion = TUNER_HAL_VERSION_1_0;
             }
         } else {
             mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
-            mTunerVersion = ((1 << 16) | 1);
+            mTunerVersion = TUNER_HAL_VERSION_1_1;
          }
      }
      return mTuner;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 744bf20..9671cf7 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -48,6 +48,10 @@
 
 namespace android {
 
+const static int TUNER_HAL_VERSION_UNKNOWN = 0;
+const static int TUNER_HAL_VERSION_1_0 = 1 << 16;
+const static int TUNER_HAL_VERSION_1_1 = (1 << 16) | 1;
+
 typedef enum {
     FRONTEND,
     LNB,
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 314bf29..7a18bd5 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -225,6 +225,7 @@
     AStorageManager_unmountObb;
     ASurfaceControl_create; # introduced=29
     ASurfaceControl_createFromWindow; # introduced=29
+    ASurfaceControl_acquire; # introduced=31
     ASurfaceControl_release; # introduced=29
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 189be80..c1b5f1d 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -185,10 +185,16 @@
     return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
 }
 
-void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
-    sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+void ASurfaceControl_acquire(ASurfaceControl* aSurfaceControl) {
+    SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
 
-    SurfaceControl_release(surfaceControl.get());
+    SurfaceControl_acquire(surfaceControl);
+}
+
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
+    SurfaceControl* surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+
+    SurfaceControl_release(surfaceControl);
 }
 
 ASurfaceTransaction* ASurfaceTransaction_create() {
diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
index 9b56b23..f4b46e9 100644
--- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
+++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
@@ -16,12 +16,15 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -40,10 +43,29 @@
     private final long mExpiryTimeMillis;
     private final boolean mCaptive;
     private final String mVenueFriendlyName;
+    private final int mVenueInfoUrlSource;
+    private final int mTermsAndConditionsSource;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = {
+            CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+            CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT})
+    public @interface CaptivePortalDataSource {}
+
+    /**
+     * Source of information: Other (default)
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0;
+
+    /**
+     * Source of information: Wi-Fi Passpoint
+     */
+    public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1;
 
     private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
             boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
-            String venueFriendlyName) {
+            String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) {
         mRefreshTimeMillis = refreshTimeMillis;
         mUserPortalUrl = userPortalUrl;
         mVenueInfoUrl = venueInfoUrl;
@@ -52,11 +74,14 @@
         mExpiryTimeMillis = expiryTimeMillis;
         mCaptive = captive;
         mVenueFriendlyName = venueFriendlyName;
+        mVenueInfoUrlSource = venueInfoUrlSource;
+        mTermsAndConditionsSource = termsAndConditionsSource;
     }
 
     private CaptivePortalData(Parcel p) {
         this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
-                p.readLong(), p.readLong(), p.readBoolean(), p.readString());
+                p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(),
+                p.readInt());
     }
 
     @Override
@@ -74,6 +99,8 @@
         dest.writeLong(mExpiryTimeMillis);
         dest.writeBoolean(mCaptive);
         dest.writeString(mVenueFriendlyName);
+        dest.writeInt(mVenueInfoUrlSource);
+        dest.writeInt(mTermsAndConditionsSource);
     }
 
     /**
@@ -88,6 +115,9 @@
         private long mExpiryTime = -1;
         private boolean mCaptive;
         private String mVenueFriendlyName;
+        private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
+        private @CaptivePortalDataSource int mUserPortalUrlSource =
+                CAPTIVE_PORTAL_DATA_SOURCE_OTHER;
 
         /**
          * Create an empty builder.
@@ -100,8 +130,8 @@
         public Builder(@Nullable CaptivePortalData data) {
             if (data == null) return;
             setRefreshTime(data.mRefreshTimeMillis)
-                    .setUserPortalUrl(data.mUserPortalUrl)
-                    .setVenueInfoUrl(data.mVenueInfoUrl)
+                    .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource)
+                    .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource)
                     .setSessionExtendable(data.mIsSessionExtendable)
                     .setBytesRemaining(data.mByteLimit)
                     .setExpiryTime(data.mExpiryTimeMillis)
@@ -123,7 +153,18 @@
          */
         @NonNull
         public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
+            return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL to be used for users to login to the portal, if captive, and the source of
+         * the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setUserPortalUrl(@Nullable Uri userPortalUrl,
+                @CaptivePortalDataSource int source) {
             mUserPortalUrl = userPortalUrl;
+            mUserPortalUrlSource = source;
             return this;
         }
 
@@ -132,7 +173,18 @@
          */
         @NonNull
         public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
+            return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER);
+        }
+
+        /**
+         * Set the URL that can be used by users to view information about the network venue, and
+         * the source of the data, see {@link CaptivePortalDataSource}
+         */
+        @NonNull
+        public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl,
+                @CaptivePortalDataSource int source) {
             mVenueInfoUrl = venueInfoUrl;
+            mVenueInfoUrlSource = source;
             return this;
         }
 
@@ -188,7 +240,8 @@
         public CaptivePortalData build() {
             return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
                     mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive,
-                    mVenueFriendlyName);
+                    mVenueFriendlyName, mVenueInfoUrlSource,
+                    mUserPortalUrlSource);
         }
     }
 
@@ -249,6 +302,22 @@
     }
 
     /**
+     * Get the information source of the Venue URL
+     * @return The source that the Venue URL was obtained from
+     */
+    public @CaptivePortalDataSource int getVenueInfoUrlSource() {
+        return mVenueInfoUrlSource;
+    }
+
+    /**
+     * Get the information source of the user portal URL
+     * @return The source that the user portal URL was obtained from
+     */
+    public @CaptivePortalDataSource int getUserPortalUrlSource() {
+        return mTermsAndConditionsSource;
+    }
+
+    /**
      * Get the venue friendly name
      */
     @Nullable
@@ -272,7 +341,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
-                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName);
+                mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName,
+                mVenueInfoUrlSource, mTermsAndConditionsSource);
     }
 
     @Override
@@ -286,7 +356,9 @@
                 && mByteLimit == other.mByteLimit
                 && mExpiryTimeMillis == other.mExpiryTimeMillis
                 && mCaptive == other.mCaptive
-                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName);
+                && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName)
+                && mVenueInfoUrlSource == other.mVenueInfoUrlSource
+                && mTermsAndConditionsSource == other.mTermsAndConditionsSource;
     }
 
     @Override
@@ -300,6 +372,8 @@
                 + ", expiryTime: " + mExpiryTimeMillis
                 + ", captive: " + mCaptive
                 + ", venueFriendlyName: " + mVenueFriendlyName
+                + ", venueInfoUrlSource: " + mVenueInfoUrlSource
+                + ", termsAndConditionsSource: " + mTermsAndConditionsSource
                 + "}";
     }
 }
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
index 9afa5d1..92a792b 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -49,17 +49,6 @@
                 }
         );
 
-        // TODO: move outside of the connectivity JAR
-        SystemServiceRegistry.registerContextAwareService(
-                Context.VPN_MANAGEMENT_SERVICE,
-                VpnManager.class,
-                (context) -> {
-                    final ConnectivityManager cm = context.getSystemService(
-                            ConnectivityManager.class);
-                    return cm.createVpnManager();
-                }
-        );
-
         SystemServiceRegistry.registerContextAwareService(
                 Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                 ConnectivityDiagnosticsManager.class,
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index a746139..92d7bf0 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkRequest.Type.LISTEN;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 import static android.net.QosCallback.QosCallbackRegistrationException;
 
 import android.annotation.CallbackExecutor;
@@ -823,6 +824,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
+
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
      * case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1068,106 +1070,55 @@
     }
 
     /**
-     * Checks if a VPN app supports always-on mode.
-     *
-     * In order to support the always-on feature, an app has to
-     * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
-     * </ul>
-     *
-     * @param userId The identifier of the user for whom the VPN app is installed.
-     * @param vpnPackage The canonical package name of the VPN app.
-     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+     * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
+    @Deprecated
     public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
-        try {
-            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage);
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
-     *
-     * <p>The designated package should declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
-     *
-     * @param userId The identifier of the user to set an always-on VPN for.
-     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
-     *                   to remove an existing always-on VPN configuration.
-     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
-     *        {@code false} otherwise.
-     * @param lockdownAllowlist The list of packages that are allowed to access network directly
-     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
-     *         this method must be called when a package that should be allowed is installed or
-     *         uninstalled.
-     * @return {@code true} if the package is set as always-on VPN controller;
-     *         {@code false} otherwise.
+    * Calls VpnManager#setAlwaysOnVpnPackageForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    @Deprecated
     public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
             boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) {
-        try {
-            return mService.setAlwaysOnVpnPackage(
-                    userId, vpnPackage, lockdownEnabled, lockdownAllowlist);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled,
+                lockdownAllowlist);
     }
 
-   /**
-     * Returns the package name of the currently set always-on VPN application.
-     * If there is no always-on VPN set, or the VPN is provided by the system instead
-     * of by an app, {@code null} will be returned.
-     *
-     * @return Package name of VPN controller responsible for always-on VPN,
-     *         or {@code null} if none is set.
+    /**
+     * Calls VpnManager#getAlwaysOnVpnPackageForUser.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    @Deprecated
     public String getAlwaysOnVpnPackageForUser(int userId) {
-        try {
-            return mService.getAlwaysOnVpnPackage(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().getAlwaysOnVpnPackageForUser(userId);
     }
 
     /**
-     * @return whether always-on VPN is in lockdown mode.
-     *
+     * Calls VpnManager#isVpnLockdownEnabled.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+     */
+    @Deprecated
     public boolean isVpnLockdownEnabled(int userId) {
-        try {
-            return mService.isVpnLockdownEnabled(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
+        return getVpnManager().isVpnLockdownEnabled(userId);
     }
 
     /**
-     * @return the list of packages that are allowed to access network when always-on VPN is in
-     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
-     *
+     * Calls VpnManager#getVpnLockdownAllowlist.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
      * @hide
-     **/
-    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        try {
-            return mService.getVpnLockdownWhitelist(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+     */
+    @Deprecated
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        return getVpnManager().getVpnLockdownAllowlist(userId);
     }
 
     /**
@@ -1220,6 +1171,45 @@
     }
 
     /**
+     * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
+     * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
+     * but is still supported for backwards compatibility.
+     * <p>
+     * This type of VPN is assumed always to use the system default network, and must always declare
+     * exactly one underlying network, which is the network that was the default when the VPN
+     * connected.
+     * <p>
+     * Calling this method with {@code true} enables legacy behaviour, specifically:
+     * <ul>
+     *     <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated
+     *     {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the
+     *     {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN
+     *     connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network
+     *     underlying the VPN.</li>
+     *     <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state
+     *     similarly replaced by the VPN network state.</li>
+     *     <li>Information on current network interfaces passed to NetworkStatsService will not
+     *     include any VPN interfaces.</li>
+     * </ul>
+     *
+     * @param enabled whether legacy lockdown VPN is enabled or disabled
+     *
+     * TODO: @SystemApi(client = MODULE_LIBRARIES)
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
+        try {
+            mService.setLegacyLockdownVpnEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
@@ -3179,20 +3169,13 @@
     }
 
     /**
-     * If the LockdownVpn mechanism is enabled, updates the vpn
-     * with a reload of its profile.
-     *
-     * @return a boolean with {@code} indicating success
-     *
-     * <p>This method can only be called by the system UID
-     * {@hide}
+     * Calls VpnManager#updateLockdownVpn.
+     * @deprecated TODO: remove when callers have migrated to VpnManager.
+     * @hide
      */
+    @Deprecated
     public boolean updateLockdownVpn() {
-        try {
-            return mService.updateLockdownVpn();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getVpnManager().updateLockdownVpn();
     }
 
     /**
@@ -3721,7 +3704,8 @@
         printStackTrace();
         checkCallbackNotNull(callback);
         Preconditions.checkArgument(
-                reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities");
+                reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null,
+                "null NetworkCapabilities");
         final NetworkRequest request;
         final String callingPackageName = mContext.getOpPackageName();
         try {
@@ -4192,8 +4176,9 @@
     }
 
     /**
-     * Registers to receive notifications about changes in the system default network. The callbacks
-     * will continue to be called until either the application exits or
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
@@ -4206,7 +4191,7 @@
      * {@link #unregisterNetworkCallback(NetworkCallback)}.
      *
      * @param networkCallback The {@link NetworkCallback} that the system will call as the
-     *                        system default network changes.
+     *                        application's default network changes.
      *                        The callback is invoked on the default internal Handler.
      * @throws RuntimeException if the app already has too many callbacks registered.
      */
@@ -4216,10 +4201,46 @@
     }
 
     /**
+     * Registers to receive notifications about changes in the application's default network. This
+     * may be a physical network or a virtual network, such as a VPN that applies to the
+     * application. The callbacks will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     *
+     * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+     * number of outstanding requests to 100 per app (identified by their UID), shared with
+     * all variants of this method, of {@link #requestNetwork} as well as
+     * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+     * Requesting a network with this method will count toward this limit. If this limit is
+     * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+     * make sure to unregister the callbacks with
+     * {@link #unregisterNetworkCallback(NetworkCallback)}.
+     *
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        application's default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @throws RuntimeException if the app already has too many callbacks registered.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+            @NonNull Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+    }
+
+    /**
      * Registers to receive notifications about changes in the system default network. The callbacks
      * will continue to be called until either the application exits or
      * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
      *
+     * This method should not be used to determine networking state seen by applications, because in
+     * many cases, most or even all application traffic may not use the default network directly,
+     * and traffic from different applications may go on different networks by default. As an
+     * example, if a VPN is connected, traffic from all applications might be sent through the VPN
+     * and not onto the system default network. Applications or system components desiring to do
+     * determine network state as seen by applications should use other methods such as
+     * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}.
+     *
      * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
      * number of outstanding requests to 100 per app (identified by their UID), shared with
      * all variants of this method, of {@link #requestNetwork} as well as
@@ -4233,20 +4254,19 @@
      *                        system default network changes.
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
      * @throws RuntimeException if the app already has too many callbacks registered.
+     *
+     * @hide
      */
-    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
-    public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+    @SystemApi(client = MODULE_LIBRARIES)
+    @SuppressLint({"ExecutorRegistration", "PairedRegistration"})
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
             @NonNull Handler handler) {
-        // This works because if the NetworkCapabilities are null,
-        // ConnectivityService takes them from the default request.
-        //
-        // Since the capabilities are exactly the same as the default request's
-        // capabilities, this request is guaranteed, at all times, to be
-        // satisfied by the same network, if any, that satisfies the default
-        // request, i.e., the system default network.
         CallbackHandler cbHandler = new CallbackHandler(handler);
         sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
-                TRACK_DEFAULT, TYPE_NONE, cbHandler);
+                TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -4519,6 +4539,8 @@
         try {
             mService.factoryReset();
             mTetheringManager.stopAllTethering();
+            // TODO: Migrate callers to VpnManager#factoryReset.
+            getVpnManager().factoryReset();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4812,9 +4834,13 @@
         return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
     }
 
-    /** @hide */
-    public VpnManager createVpnManager() {
-        return new VpnManager(mContext, mService);
+    /**
+     * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a
+     * private final mVpnManager because ConnectivityManager is initialized before VpnManager.
+     * @hide TODO: remove.
+     */
+    public VpnManager getVpnManager() {
+        return mContext.getSystemService(VpnManager.class);
     }
 
     /** @hide */
@@ -4848,15 +4874,6 @@
         }
     }
 
-    private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        try {
-            mService.setOemNetworkPreference(preference);
-        } catch (RemoteException e) {
-            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     @NonNull
     private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>();
 
@@ -5058,4 +5075,60 @@
         sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
                 TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler));
     }
+
+    /**
+     * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor,
+     * OnSetOemNetworkPreferenceListener)}.
+     * @hide
+     */
+    @SystemApi
+    public interface OnSetOemNetworkPreferenceListener {
+        /**
+         * Called when setOemNetworkPreference() successfully completes.
+         */
+        void onComplete();
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param executor the executor on which listener will be invoked.
+     * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to
+     *                  communicate completion of setOemNetworkPreference(). This will only be
+     *                  called once upon successful completion of setOemNetworkPreference().
+     * @throws IllegalArgumentException if {@code preference} contains invalid preference values.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws UnsupportedOperationException if called on a non-automotive device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE)
+    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference,
+            @Nullable @CallbackExecutor final Executor executor,
+            @Nullable final OnSetOemNetworkPreferenceListener listener) {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (null != listener) {
+            Objects.requireNonNull(executor, "Executor must be non-null");
+        }
+        final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null :
+                new IOnSetOemNetworkPreferenceListener.Stub() {
+                    @Override
+                    public void onComplete() {
+                        executor.execute(listener::onComplete);
+                    }
+        };
+
+        try {
+            mService.setOemNetworkPreference(preference, listenerInternal);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index f909d13..6391802 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -20,6 +20,7 @@
 import android.net.ConnectionInfo;
 import android.net.ConnectivityDiagnosticsManager;
 import android.net.IConnectivityDiagnosticsCallback;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.LinkProperties;
@@ -42,9 +43,6 @@
 import android.os.ResultReceiver;
 
 import com.android.connectivity.aidl.INetworkAgent;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
 
 /**
  * Interface that answers queries about, and allows changing, the
@@ -122,35 +120,8 @@
 
     ProxyInfo getProxyForNetwork(in Network nework);
 
-    boolean prepareVpn(String oldPackage, String newPackage, int userId);
-
-    void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
-
-    ParcelFileDescriptor establishVpn(in VpnConfig config);
-
-    boolean provisionVpnProfile(in VpnProfile profile, String packageName);
-
-    void deleteVpnProfile(String packageName);
-
-    void startVpnProfile(String packageName);
-
-    void stopVpnProfile(String packageName);
-
-    VpnConfig getVpnConfig(int userId);
-
-    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    void startLegacyVpn(in VpnProfile profile);
-
-    LegacyVpnInfo getLegacyVpnInfo(int userId);
-
-    boolean updateLockdownVpn();
-    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
-    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
-            in List<String> lockdownWhitelist);
-    String getAlwaysOnVpnPackage(int userId);
-    boolean isVpnLockdownEnabled(int userId);
-    List<String> getVpnLockdownWhitelist(int userId);
     void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
+    void setLegacyLockdownVpnEnabled(boolean enabled);
 
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
@@ -199,10 +170,6 @@
 
     int getRestoreDefaultNetworkDelay(int networkType);
 
-    boolean addVpnAddress(String address, int prefixLength);
-    boolean removeVpnAddress(String address, int prefixLength);
-    boolean setUnderlyingNetworksForVpn(in Network[] networks);
-
     void factoryReset();
 
     void startNattKeepalive(in Network network, int intervalSeconds,
@@ -222,8 +189,6 @@
     byte[] getNetworkWatchlistConfigHash();
 
     int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
-    boolean isCallerCurrentAlwaysOnVpnApp();
-    boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 
     void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
             in NetworkRequest request, String callingPackageName);
@@ -245,5 +210,6 @@
     void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
     void unregisterQosCallback(in IQosCallback callback);
 
-    void setOemNetworkPreference(in OemNetworkPreferences preference);
+    void setOemNetworkPreference(in OemNetworkPreferences preference,
+            in IOnSetOemNetworkPreferenceListener listener);
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 55b2c3c..26d14cb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -762,12 +762,14 @@
         final int originalSignalStrength = mSignalStrength;
         final int originalOwnerUid = getOwnerUid();
         final int[] originalAdministratorUids = getAdministratorUids();
+        final TransportInfo originalTransportInfo = getTransportInfo();
         clearAll();
         mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS)
                 | (1 << TRANSPORT_TEST);
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
+        mTransportInfo = originalTransportInfo;
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
@@ -2083,9 +2085,10 @@
     /**
      * Check if private dns is broken.
      *
-     * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+     * @return {@code true} if private DNS is broken on this network.
      * @hide
      */
+    @SystemApi
     public boolean isPrivateDnsBroken() {
         return mPrivateDnsBroken;
     }
@@ -2328,6 +2331,17 @@
         }
 
         /**
+         * Completely clears the contents of this object, removing even the capabilities that are
+         * set by default when the object is constructed.
+         * @return this builder
+         */
+        @NonNull
+        public Builder clearAll() {
+            mCaps.clearAll();
+            return this;
+        }
+
+        /**
          * Sets the owner UID.
          *
          * The default value is {@link Process#INVALID_UID}. Pass this value to reset.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 6540397..4e3085f 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -16,22 +16,6 @@
 
 package android.net;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -46,8 +30,6 @@
 import android.text.TextUtils;
 import android.util.proto.ProtoOutputStream;
 
-import java.util.Arrays;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -104,17 +86,14 @@
      *       callbacks about the single, highest scoring current network
      *       (if any) that matches the specified NetworkCapabilities, or
      *
-     *     - TRACK_DEFAULT, a hybrid of the two designed such that the
-     *       framework will issue callbacks for the single, highest scoring
-     *       current network (if any) that matches the capabilities of the
-     *       default Internet request (mDefaultRequest), but which cannot cause
-     *       the framework to either create or retain the existence of any
-     *       specific network. Note that from the point of view of the request
-     *       matching code, TRACK_DEFAULT is identical to REQUEST: its special
-     *       behaviour is not due to different semantics, but to the fact that
-     *       the system will only ever create a TRACK_DEFAULT with capabilities
-     *       that are identical to the default request's capabilities, thus
-     *       causing it to share fate in every way with the default request.
+     *     - TRACK_DEFAULT, which causes the framework to issue callbacks for
+     *       the single, highest scoring current network (if any) that will
+     *       be chosen for an app, but which cannot cause the framework to
+     *       either create or retain the existence of any specific network.
+     *
+     *     - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks
+     *       for the network (if any) that satisfies the default Internet
+     *       request.
      *
      *     - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
      *       to retain the NET_CAPABILITY_FOREGROUND capability. A network with
@@ -137,6 +116,7 @@
         TRACK_DEFAULT,
         REQUEST,
         BACKGROUND_REQUEST,
+        TRACK_SYSTEM_DEFAULT,
     };
 
     /**
@@ -174,30 +154,8 @@
      * needed in terms of {@link NetworkCapabilities} features
      */
     public static class Builder {
-        /**
-         * Capabilities that are currently compatible with VCN networks.
-         */
-        private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList(
-                NET_CAPABILITY_CAPTIVE_PORTAL,
-                NET_CAPABILITY_DUN,
-                NET_CAPABILITY_FOREGROUND,
-                NET_CAPABILITY_INTERNET,
-                NET_CAPABILITY_NOT_CONGESTED,
-                NET_CAPABILITY_NOT_METERED,
-                NET_CAPABILITY_NOT_RESTRICTED,
-                NET_CAPABILITY_NOT_ROAMING,
-                NET_CAPABILITY_NOT_SUSPENDED,
-                NET_CAPABILITY_NOT_VPN,
-                NET_CAPABILITY_PARTIAL_CONNECTIVITY,
-                NET_CAPABILITY_TEMPORARILY_NOT_METERED,
-                NET_CAPABILITY_TRUSTED,
-                NET_CAPABILITY_VALIDATED);
-
         private final NetworkCapabilities mNetworkCapabilities;
 
-        // A boolean that represents the user modified NOT_VCN_MANAGED capability.
-        private boolean mModifiedNotVcnManaged = false;
-
         /**
          * Default constructor for Builder.
          */
@@ -219,7 +177,6 @@
             // maybeMarkCapabilitiesRestricted() doesn't add back.
             final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
             nc.maybeMarkCapabilitiesRestricted();
-            deduceNotVcnManagedCapability(nc);
             return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
                     ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
         }
@@ -236,9 +193,6 @@
          */
         public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
-            if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
-            }
             return this;
         }
 
@@ -250,9 +204,6 @@
          */
         public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
-            if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
-                mModifiedNotVcnManaged = true;
-            }
             return this;
         }
 
@@ -310,9 +261,6 @@
         @NonNull
         public Builder clearCapabilities() {
             mNetworkCapabilities.clearAll();
-            // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
-            // should not be add back later.
-            mModifiedNotVcnManaged = true;
             return this;
         }
 
@@ -432,25 +380,6 @@
             mNetworkCapabilities.setSignalStrength(signalStrength);
             return this;
         }
-
-        /**
-         * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities
-         * and user intention, which includes:
-         *   1. For the requests that don't have anything besides
-         *      {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
-         *      allow the callers automatically utilize VCN networks if available.
-         *   2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
-         *      do not alter them to allow user fire request that suits their need.
-         *
-         * @hide
-         */
-        private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
-            if (mModifiedNotVcnManaged) return;
-            for (final int cap : nc.getCapabilities()) {
-                if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
-            }
-            nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        }
     }
 
     // implement the Parcelable interface
@@ -601,6 +530,8 @@
                 return NetworkRequestProto.TYPE_REQUEST;
             case BACKGROUND_REQUEST:
                 return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
+            case TRACK_SYSTEM_DEFAULT:
+                return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT;
             default:
                 return NetworkRequestProto.TYPE_UNKNOWN;
         }
diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
new file mode 100644
index 0000000..0242ba0
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.util.Objects;
+
+/**
+ * Container for VPN-specific transport information.
+ *
+ * @see android.net.TransportInfo
+ * @see NetworkCapabilities#getTransportInfo()
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class VpnTransportInfo implements TransportInfo, Parcelable {
+    private static final SparseArray<String> sTypeToString =
+            MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
+
+    /** Type of this VPN. */
+    @VpnManager.VpnType public final int type;
+
+    public VpnTransportInfo(@VpnManager.VpnType int type) {
+        this.type = type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof VpnTransportInfo)) return false;
+
+        VpnTransportInfo that = (VpnTransportInfo) o;
+        return this.type == that.type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type);
+    }
+
+    @Override
+    public String toString() {
+        final String typeString = sTypeToString.get(type, "VPN_TYPE_???");
+        return String.format("VpnTransportInfo{%s}", typeString);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(type);
+    }
+
+    public static final @NonNull Creator<VpnTransportInfo> CREATOR =
+            new Creator<VpnTransportInfo>() {
+        public VpnTransportInfo createFromParcel(Parcel in) {
+            return new VpnTransportInfo(in.readInt());
+        }
+        public VpnTransportInfo[] newArray(int size) {
+            return new VpnTransportInfo[size];
+        }
+    };
+}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 8fc3181..ed1716f 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,6 @@
     ],
     srcs: [
         "jni/com_android_server_TestNetworkService.cpp",
-        "jni/com_android_server_connectivity_Vpn.cpp",
         "jni/onload.cpp",
     ],
     shared_libs: [
diff --git a/packages/Connectivity/service/jni/onload.cpp b/packages/Connectivity/service/jni/onload.cpp
index 3afcb0e..0012879 100644
--- a/packages/Connectivity/service/jni/onload.cpp
+++ b/packages/Connectivity/service/jni/onload.cpp
@@ -19,7 +19,6 @@
 
 namespace android {
 
-int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_TestNetworkService(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -29,12 +28,11 @@
         return JNI_ERR;
     }
 
-    if (register_android_server_connectivity_Vpn(env) < 0
-        || register_android_server_TestNetworkService(env) < 0) {
+    if (register_android_server_TestNetworkService(env) < 0) {
         return JNI_ERR;
     }
 
     return JNI_VERSION_1_6;
 }
 
-};
\ No newline at end of file
+};
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 252670a..8e1774b 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
 svetoslavganov@google.com
-moltmann@google.com
 toddke@google.com
 suprabh@google.com
 
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
new file mode 100644
index 0000000..46e2d45
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"
+        android:fillAlpha="0.3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
new file mode 100644
index 0000000..d9cd590
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
new file mode 100644
index 0000000..e80fd08
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M13,8h2v3h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.5,5h2v6h-2z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,3h2v8h-2z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
new file mode 100644
index 0000000..493912b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_1.xml
@@ -0,0 +1,35 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"
+        android:fillAlpha="0.3"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
new file mode 100644
index 0000000..af677fb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_2.xml
@@ -0,0 +1,34 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"
+        android:fillAlpha="0.3"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
new file mode 100644
index 0000000..68b39da
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_call_strength_3.xml
@@ -0,0 +1,33 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.05,9.59C17.69,9.22 17.19,9 16.64,9c-0.55,0 -1.05,0.22 -1.41,0.59L16.64,11L18.05,9.59z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,7.5c0.96,0 1.84,0.39 2.47,1.03l1.42,-1.42c-1,-1 -2.37,-1.61 -3.89,-1.61c-1.52,0 -2.89,0.62 -3.89,1.61l1.42,1.42C14.8,7.89 15.67,7.5 16.64,7.5z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M16.64,4c1.93,0 3.68,0.79 4.95,2.05L23,4.64C21.37,3.01 19.12,2 16.64,2c-2.49,0 -4.74,1.01 -6.36,2.64l1.42,1.42C12.96,4.79 14.71,4 16.64,4z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 889980a..577fb66 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skep tans nuwe gebruiker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bynaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Beëindig gastesessie"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gas"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data, drie stawe."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasein vol."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet is ontkoppel."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet gekoppel."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index c41e4d5..c554e2e 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"አዲስ ተጠቃሚ በመፍጠር ላይ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ቅጽል ስም"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"የእንግዳ ክፍለ-ጊዜ ጨርስ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"የውሂብ ሦስት አሞሌዎች።"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"የውሂብ አመልካች ሙሉ ነው።"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ኤተርኔት ተነቅሏል።"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ኤተርኔት ተገናኝቷል።"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ኢተርኔት።"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index f8d1d57..b23cebd 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -558,7 +558,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"جارٍ إنشاء مستخدم جديد…"</string>
     <string name="user_nickname" msgid="262624187455825083">"اللقب"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"إنهاء جلسة الضيف"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string>
@@ -595,5 +595,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"إشارة البيانات تتكون من ثلاثة أشرطة."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"إشارة البيانات كاملة."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"‏تم قطع اتصال Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"‏تم إنشاء اتصال Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"إيثرنت"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index c6078f8..2380b2f 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যৱহাৰকাৰী সৃষ্টি কৰি থকা হৈছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"অতিথিৰ ছেশ্বন সমাপ্ত কৰক"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ডেটা ছিংগনেলত তিনিডাল দণ্ড আছে।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ডেটা ছিগনেল পূৰা আছে।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথাৰনেট সংযোগ বিচ্ছিন্ন হৈছে।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথাৰনেট সংযোগ হৈছে।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথাৰনেট।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index d3e0a25..f70bb84 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni istifadəçi yaradılır…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ləqəb"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Qonaq sessiyasını bitirin"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data üç xətdir."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data siqnalı tamdır."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kəsilib."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet qoşuludur."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 8541222..749f864 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Pravi se novi korisnik…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal za podatke od tri crte."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za podatke je najjači."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa eternetom je prekinuta."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Eternet je povezan."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index aab600f..6b2740d 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ствараецца новы карыстальнік…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псеўданім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завяршыць гасцявы сеанс"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Госць"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"3 планкі дадзеных."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Поўны сігнал перадачы дадзеных."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet адлучаны."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet падлучаны."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 92f2935..1a24b9e 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Създава се нов потребител…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Прекратяване на сесията като гост"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Данните са с три чертички."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналът за данни е пълен."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Връзката с Ethernet е прекратена."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Установена е връзка с Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index b40e24e..1c9f84f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"নতুন ব্যবহারকারী তৈরি করা হচ্ছে…"</string>
     <string name="user_nickname" msgid="262624187455825083">"বিশেষ নাম"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"গেস্ট সেশন শেষ করুন"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"তিন দন্ড ডেটার সংকেত৷"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"পূর্ণ ডেটার সংকেত রয়েছে৷"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ইথারনেটের সংযোগ বিচ্ছিন্ন হয়েছে৷"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ইথারনেট সংযুক্ত হয়েছে৷"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ইথারনেট।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index cec2e45..f701421 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kreiranje novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi sesiju gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Prijenos podataka na tri crtice."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal za prijenos podataka pun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Veza sa Ethernetom je prekinuta."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet je spojen."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 6134da8..78ef109 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"S\'està creant l\'usuari…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Àlies"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalitza la sessió de convidat"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Senyal de dades: tres barres."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Senyal de dades: complet."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"S\'ha desconnectat l\'Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connectada"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index f29a3dd..8ca9800 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytváření nového uživatele…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Přezdívka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončení relace hosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Host"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tři čárky signálu datové sítě."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál datové sítě."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Síť ethernet je odpojena."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Síť ethernet je připojena."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index d92d41d..5d55a12 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Opretter ny bruger…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kaldenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Afslut gæstesessionen"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tre bjælker."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal fuldt."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er ikke tilsluttet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilsluttet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index ac05b620..1fbb391 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Neuer Nutzer wird erstellt…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alias"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsitzung beenden"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datensignal - drei Balken"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Volle Datensignalstärke"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet nicht verbunden"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbunden"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 0b11d69..32bcf87 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Δημιουργία νέου χρήστη…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ψευδώνυμο"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Λήξη περιόδου σύνδεσης επισκέπτη"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Τρεις γραμμές δεδομένων."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Πλήρες σήμα δεδομένων."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Το Ethernet αποσυνδέθηκε."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Το Ethernet συνδέθηκε."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index aa0d3f1..198fee4 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index b98c4b8..6241953 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"End Guest session"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data three bars."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Data signal full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet disconnected."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connected."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"No calling."</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index c01f3a0..27cd8b3 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎Creating new user…‎‏‎‎‏‎"</string>
     <string name="user_nickname" msgid="262624187455825083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎Nickname‎‏‎‎‏‎"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎Add guest‎‏‎‎‏‎"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎End guest session‎‏‎‎‏‎"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎Remove guest‎‏‎‎‏‎"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎Guest‎‏‎‎‏‎"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎Take a photo‎‏‎‎‏‎"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎Choose an image‎‏‎‎‏‎"</string>
@@ -591,5 +591,6 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎Data three bars.‎‏‎‎‏‎"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎Data signal full.‎‏‎‎‏‎"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎Ethernet disconnected.‎‏‎‎‏‎"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎Ethernet connected.‎‏‎‎‏‎"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎Ethernet.‎‏‎‎‏‎"</string>
+    <string name="accessibility_no_calling" msgid="3540827068323895748">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎No calling.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 1da8e37..0f76576 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Sobrenombre"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos completa"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 54226ce..abd209c 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apodo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Señal de datos al máximo"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Conexión Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conexión Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 4c747e3..2e9f3b8 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Uue kasutaja loomine …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Hüüdnimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Lõpeta külastajaseanss"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Andmeside: kolm pulka."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Andmesignaal on tugev."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Etherneti-ühendus on katkestatud."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Etherneti-ühendus on loodud."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 6016cd2..ecb4f75 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Beste erabiltzaile bat sortzen…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Amaitu gonbidatuentzako saioa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datu-seinaleak hiru barra ditu."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datu-seinale osoa."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bidezko konexioa eten da."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bidez konektatu da."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index e855570..56ade54 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"درحال ایجاد کاربر جدید…"</string>
     <string name="user_nickname" msgid="262624187455825083">"نام مستعار"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"پایان دادن به جلسه مهمان"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"سه نوار برای داده."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"قدرت سیگنال داده کامل است."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"اترنت قطع شد."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"اترنت متصل شد."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"اترنت."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 724e52a..e26ecb7 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Luodaan uutta käyttäjää…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Lempinimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Lopeta Vierailija-käyttökerta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Datasignaali - kolme palkkia"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Vahva kuuluvuus."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet on irrotettu."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet on yhdistetty."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 74c3005..d61ebc0 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Créer un utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Mettre fin à la session d\'invité"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index c9651ce..64c06fa 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Création d\'un nouvel utilisateur…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Fermer la session Invité"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Signal bon"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Signal excellent"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet déconnecté"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet connecté"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f499f58..cf63dd7 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario novo…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Alcume"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Finalizar sesión de invitado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tres barras de sinal de datos"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de datos: completo"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Desconectouse a Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Conectouse a Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 23ee1de..427ce56 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"નવા વપરાશકર્તા બનાવી રહ્યાં છીએ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ઉપનામ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"અતિથિ સત્ર સમાપ્ત કરો"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ડેટા ત્રણ બાર."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ડેટા સિગ્નલ પૂર્ણ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ઇથરનેટ ડિસ્કનેક્ટ થયું."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ઇથરનેટ કનેક્ટ થયું."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ઇથરનેટ."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index a2fc43c..159d2db 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नया उपयोगकर्ता बनाया जा रहा है…"</string>
     <string name="user_nickname" msgid="262624187455825083">"प्रचलित नाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"मेहमान के तौर पर ब्राउज़ करने का सेशन खत्म करें"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सि‍ग्‍नल पूरा."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ईथरनेट डिस्‍कनेक्‍ट किया गया."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ईथरनेट कनेक्‍ट किया गया."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ईथरनेट."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 396051d..5e33ef3 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Izrada novog korisnika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Završi gostujuću sesiju"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatkovni signal tri stupca."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal pun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Prekinuta je veza s ethernetom."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Uspostavljena je veza s ethernetom."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 0c9bc54..28975be 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Új felhasználó létrehozása…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Becenév"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"A vendég munkamenet befejezése"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Adat három sáv."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Adatjel teljes."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet leválasztva."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet csatlakoztatva."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 56c93b6..a207526 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ստեղծվում է օգտատիրոջ նոր պրոֆիլ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Կեղծանուն"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ավարտել հյուրի աշխատաշրջանը"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Տվյալների երեք գիծ:"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Տվյալների ազդանշանը լրիվ է:"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet-ը անջատված է:"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet-ը կապակցված է:"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet։"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 0440f47..c4d752d 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Membuat pengguna baru …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Akhiri sesi tamu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga batang."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinyal data penuh."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet terputus."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tersambung."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 0bb294f..56b0a81 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Stofnar nýjan notanda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Gælunafn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ljúka gestalotu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sendistyrkur gagnatengingar er þrjú strik."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Fullur sendistyrkur gagnatengingar."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet aftengt."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet tengt."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 54049d7..f045d27 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creazione nuovo utente…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Termina sessione Ospite"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string>
@@ -577,7 +577,7 @@
     <string name="data_connection_4g_plus" msgid="5194902328408751020">"4G+"</string>
     <string name="data_connection_lte" msgid="7675461204366364124">"LTE"</string>
     <string name="data_connection_lte_plus" msgid="6643158654804916653">"LTE+"</string>
-    <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"Wi-Fi operatore"</string>
+    <string name="data_connection_carrier_wifi" msgid="2250268321065848954">"CWF"</string>
     <string name="cell_data_off_content_description" msgid="2280700839891636498">"Dati mobili disattivati"</string>
     <string name="not_default_data_content_description" msgid="6517068332106592887">"Non impostato per l\'utilizzo dei dati"</string>
     <string name="accessibility_no_phone" msgid="2687419663127582503">"Nessun telefono."</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: tre barre."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Massimo segnale dati."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Connessione Ethernet annullata."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Connessione Ethernet stabilita."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 6cc4347..ba3a467 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"בתהליך יצירה של משתמש חדש…"</string>
     <string name="user_nickname" msgid="262624187455825083">"כינוי"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"הפסקת הגלישה כאורח"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"אורח"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"שלושה פסים של נתונים."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"אות הנתונים מלא."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"אתרנט מנותק."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"אתרנט מחובר."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"אתרנט."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index ec674ad..6f398b6 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"新しいユーザーを作成しています…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ニックネーム"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ゲスト セッションを終了する"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"データ信号:レベル3"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"データ信号:フル"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"イーサネット接続を解除しました。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"イーサネットに接続しました。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"イーサネット。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 30ab3b4..5bf1dd0 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
     <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"სტუმრის სესიის დასრულება"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"მონაცემების გადაცემა: სამი ზოლი"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"მონაცემთა გადაცემის საიმედო სიგნალი."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet კავშირი შეწყვეტილია."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet დაკავშირებულია."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index ef78527..c7c3837 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңа пайдаланушы профилі жасалуда…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Лақап ат"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Қонақ сеансын аяқтау"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дерекқор үш баған."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дерекқор сигналы толы."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажыратылған."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet қосылған."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index ed59c74..99a73e1 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"កំពុងបង្កើត​អ្នកប្រើប្រាស់ថ្មី…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ឈ្មោះ​ហៅក្រៅ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូល​ភ្ញៀវ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"បញ្ចប់វគ្គភ្ញៀវ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"លុប​​​ភ្ញៀវ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើស​រូបភាព"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ទិន្នន័យ​បី​កាំ។"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"សញ្ញា​ទិន្នន័យ​ពេញ។"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"បានផ្តាច់អ៊ីសឺរណិត។"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"បានភ្ជាប់អ៊ីសឺរណិត។"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"អ៊ីសឺរណិត។"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 995dc86..54a42a8 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲಾಗುತ್ತಿದೆ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ಅಡ್ಡ ಹೆಸರು"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ಅತಿಥಿ ಸೆಷನ್ ಅಂತ್ಯಗೊಳಿಸಿ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ಡೇಟಾ ಮೂರು ಪಟ್ಟಿಗಳು."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ಡೇಟಾ ಸಂಕೇತ ತುಂಬಿದೆ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ಇಥರ್ನೆಟ್ ಸಂಪರ್ಕಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ಇಥರ್ನೆಟ್."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 7863d48..9e9fea9 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"새로운 사용자를 만드는 중…"</string>
     <string name="user_nickname" msgid="262624187455825083">"닉네임"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"게스트 세션 종료"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"게스트"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"데이터 신호 막대가 세 개입니다."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"데이터 신호가 강합니다."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"이더넷에서 연결 해제되었습니다."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"이더넷에 연결되었습니다."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"이더넷에 연결되었습니다."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ky/arrays.xml b/packages/SettingsLib/res/values-ky/arrays.xml
index 6af32cc..7c0fbae 100644
--- a/packages/SettingsLib/res/values-ky/arrays.xml
+++ b/packages/SettingsLib/res/values-ky/arrays.xml
@@ -86,7 +86,7 @@
     <item msgid="8147982633566548515">"карта14"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_titles">
-    <item msgid="2494959071796102843">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2494959071796102843">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="4055460186095649420">"SBC"</item>
     <item msgid="720249083677397051">"AAC"</item>
     <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -94,7 +94,7 @@
     <item msgid="3825367753087348007">"LDAC"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_summaries">
-    <item msgid="8868109554557331312">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="8868109554557331312">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="9024885861221697796">"SBC"</item>
     <item msgid="4688890470703790013">"AAC"</item>
     <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> аудио"</item>
@@ -102,38 +102,38 @@
     <item msgid="2553206901068987657">"LDAC"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
-    <item msgid="926809261293414607">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="926809261293414607">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8003118270854840095">"44,1 кГц"</item>
     <item msgid="3208896645474529394">"48,0 кГц"</item>
     <item msgid="8420261949134022577">"88,2 кГц"</item>
     <item msgid="8887519571067543785">"96,0 кГц"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
-    <item msgid="2284090879080331090">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2284090879080331090">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="1872276250541651186">"44,1 кГц"</item>
     <item msgid="8736780630001704004">"48,0 кГц"</item>
     <item msgid="7698585706868856888">"88,2 кГц"</item>
     <item msgid="8946330945963372966">"96,0 кГц"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
-    <item msgid="2574107108483219051">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="2574107108483219051">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="4671992321419011165">"16 бит/үлгү"</item>
     <item msgid="1933898806184763940">"24 бит/үлгү"</item>
     <item msgid="1212577207279552119">"32 бит/үлгү"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
-    <item msgid="9196208128729063711">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="9196208128729063711">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="1084497364516370912">"16 бит/үлгү"</item>
     <item msgid="2077889391457961734">"24 бит/үлгү"</item>
     <item msgid="3836844909491316925">"32 бит/үлгү"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
-    <item msgid="3014194562841654656">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="3014194562841654656">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="5982952342181788248">"Моно"</item>
     <item msgid="927546067692441494">"Стерео"</item>
   </string-array>
   <string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
-    <item msgid="1997302811102880485">"Тутум тандаганды колдонуу (демейки)"</item>
+    <item msgid="1997302811102880485">"Система тандаганды колдонуу (демейки)"</item>
     <item msgid="8005696114958453588">"Моно"</item>
     <item msgid="1333279807604675720">"Стерео"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 45d05c6..2aec5cb 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -143,7 +143,7 @@
     <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
     <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Алынып салынган колдонмолор"</string>
     <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Өчүрүлгөн колдонмолор жана колдонуучулар"</string>
-    <string name="data_usage_ota" msgid="7984667793701597001">"Тутум жаңыртуулары"</string>
+    <string name="data_usage_ota" msgid="7984667793701597001">"Системанын жаңыртуулары"</string>
     <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB модем"</string>
     <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Wi-Fi байланыш түйүнү"</string>
     <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth модем"</string>
@@ -162,7 +162,7 @@
     <string name="tts_default_pitch_title" msgid="6988592215554485479">"Негизги тон"</string>
     <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Синтезделген кептин интонациясына таасирин тийгизет"</string>
     <string name="tts_default_lang_title" msgid="4698933575028098940">"Тил"</string>
-    <string name="tts_lang_use_system" msgid="6312945299804012406">"Тутум тилин колдонуу"</string>
+    <string name="tts_lang_use_system" msgid="6312945299804012406">"Системанын тилин колдонуу"</string>
     <string name="tts_lang_not_selected" msgid="7927823081096056147">"Тил тандалган жок"</string>
     <string name="tts_default_lang_summary" msgid="9042620014800063470">"Текстти окуй турган тилди тандоо"</string>
     <string name="tts_play_example_title" msgid="1599468547216481684">"Үлгүнү угуу"</string>
@@ -483,7 +483,7 @@
     <string name="retail_demo_reset_next" msgid="3688129033843885362">"Кийинки"</string>
     <string name="retail_demo_reset_title" msgid="1866911701095959800">"Сырсөз талап кылынат"</string>
     <string name="active_input_method_subtypes" msgid="4232680535471633046">"Жигердүү киргизүү ыкмалары"</string>
-    <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Тутум тилдерин колдонуу"</string>
+    <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Системанын тилдерин колдонуу"</string>
     <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> тууралоолору ачылган жок"</string>
     <string name="ime_security_warning" msgid="6547562217880551450">"Бул киргизүү ыкмасы сиз терген бардык тексттер, сырсөздөр жана кредиттик  карталар сыяктуу жеке маалыматтарды кошо чогултушу мүмкүн. Бул <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> колдонмосу менен байланыштуу. Ушул киргизүү ыкма колдонулсунбу?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Эскертүү: Өчүрүп-күйгүзгөндөн кийин, бул колдонмо телефондун кулпусу ачылмайынча иштебейт"</string>
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Жаңы колдонуучу түзүлүүдө…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Конок сеансын бүтүрүү"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Конок"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Мобилдик интернеттин сигналы үч таякча."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Мобилдик интернеттин сигналы толук."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet ажырады."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet туташты."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index ca6e06c..e199e1f 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ກຳລັງສ້າງຜູ້ໃຊ້ໃໝ່…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ຊື່ຫຼິ້ນ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ສິ້ນສຸດເຊດຊັນແຂກ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ຂໍ້ມູນສາມຂີດ."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ສັນ​ຍານຂໍ້ມູນ​ເຕັມ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ອີ​ເທີ​ເນັດ​ຕັດ​ເຊື່ອມ​ຕໍ່​ແລ້ວ."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ອີ​ເທີ​ເນັດ​ເຊື່ອມ​ຕໍ່​ແລ້ວ."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ອີເທີເນັດ."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 173b57e..a25c59f5 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Kuriamas naujas naudotojas…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Slapyvardis"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Baigti svečio sesiją"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Trys duomenų juostos."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Stiprus duomenų signalas."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Atsijungta nuo eterneto."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Prijungta prie eterneto."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternetas."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index a8bd2cc..f360c90 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Notiek jauna lietotāja izveide…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Segvārds"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Beigt viesa sesiju"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dati: trīs joslas."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Pilna piekļuve datu signālam."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Pārtraukts savienojums ar tīklu Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Izveidots savienojums ar tīklu Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Tīkls Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0ef3f0e..a32d00b 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Се создава нов корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Прекар"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши ја гостинската сесија"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Податоци три цртички."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигналот за податоци е исполнет."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Етернетот е исклучен."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернетот е поврзан."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e6289ae..902507d 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"പുതിയ ഉപയോക്താവിനെ സൃഷ്‌ടിക്കുന്നു…"</string>
     <string name="user_nickname" msgid="262624187455825083">"വിളിപ്പേര്"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"അതിഥി സെഷൻ അവസാനിപ്പിക്കുക"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ഡാറ്റ മൂന്ന് ബാർ."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ഡാറ്റ സിഗ്‌നൽ പൂർണ്ണമാണ്."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ഇതർനെറ്റ് വിച്ഛേദിച്ചു."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ഇതർനെറ്റ് കണക്റ്റുചെയ്‌തു."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ഇതർനെറ്റ്."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index ead712c..83009b7 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Шинэ хэрэглэгч үүсгэж байна…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Хоч"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Зочны сургалтыг дуусгах"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Дата гурван баганатай."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Дата дохио дүүрэн."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet саллаа."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet холбогдсон."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Этернэт."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index edcc71b..359f525 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नवीन वापरकर्ता तयार करत आहे…"</string>
     <string name="user_nickname" msgid="262624187455825083">"टोपणनाव"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथी सत्र संपवा"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तीन बार."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा सिग्नल पूर्ण."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट डिस्कनेक्ट केले."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट कनेक्ट केले."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 8a14437..eba89da 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Mencipta pengguna baharu…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nama panggilan"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Tamatkan sesi tetamu"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data tiga bar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Isyarat data penuh."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet diputuskan sambungan."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet disambungkan."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 377c8b2..e982aa9 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"အသုံးပြုသူအသစ် ပြုလုပ်နေသည်…"</string>
     <string name="user_nickname" msgid="262624187455825083">"နာမည်ပြောင်"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ဧည့်သည်ဆက်ရှင်ကို အဆုံးသတ်ရန်"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ဒေတာသုံးဘား။"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ဒေတာထုတ်လွှင့်မှုအပြည့်ဖမ်းမိခြင်း"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet နှင့်ချိတ်ဆက်မှုပြတ်တောက်"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ချိတ်ဆက်ထား။"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"အီသာနက်။"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index f0e773b..abbb5e7 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Oppretter en ny bruker …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Kallenavn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Avslutt gjesteøkten"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data – tre stolper."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignal er fullt."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet er frakoblet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet er tilkoblet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 0bde70f..d7a32bf 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"नयाँ प्रयोगकर्ता बनाउँदै…"</string>
     <string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"अतिथिको सत्र अन्त्य गर्नुहोस्"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"डेटा तिन बाधाहरू।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"डेटा संकेत पूर्ण।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"इथरनेट विच्छेद भयो।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"इथरनेट जोडियो।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"इथरनेट।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 2bc1e8c..7e739bc 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Nieuwe gebruiker maken…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Bijnaam"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Gastsessie beëindigen"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Gegevens: drie streepjes."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Gegevenssignaal is op volle sterkte."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetverbinding verbroken."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet verbonden."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 787c871..1e842b7 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ନୂଆ ଉପଯୋଗକର୍ତ୍ତା ତିଆରି କରାଯାଉଛି…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ଡାକନାମ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ଅତିଥି ସେସନ୍ ଶେଷ କରନ୍ତୁ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ଡାଟାର ତିନୋଟି ବାର୍‍ ଅଛି।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ଡାଟା ସିଗ୍ନାଲ୍ ପୂର୍ଣ୍ଣ ଅଛି।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ଇଥରନେଟ୍‍ ବିଚ୍ଛିନ୍ନ ହୋଇଛି।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ଇଥରନେଟ୍‍ ସଂଯୁକ୍ତ ହୋଇଛି।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ଇଥରନେଟ୍।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 9483e06..d035fc0 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ਉਪਨਾਮ"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਸਮਾਪਤ ਕਰੋ"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">" ਡਾਟਾ  ਤਿੰਨ ਬਾਰ।"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">" ਡਾਟਾ  ਸਿਗਨਲ ਪੂਰਾ।"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ਈਥਰਨੈੱਟ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ਈਥਰਨੈੱਟ ਕਨੈਕਟ ਹੋ ਗਿਆ।"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ਈਥਰਨੈੱਟ।"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index c7f1595..8a732cb 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Tworzę nowego użytkownika…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Kończenie sesji gościa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gość"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Dane: trzy paski."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Dane: pełna moc sygnału."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Rozłączono z siecią Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Połączono z siecią Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 94cad918..ab23059 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"A criar novo utilizador…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudónimo"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Terminar a sessão de convidado"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados completo."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desligada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet ligada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 881bccb..a63b9b5 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Encerrar sessão de visitante"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Três barras do sinal de dados."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinal de dados cheio."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet desconectada."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectada."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 63d8b8f..98edb32 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Se creează un utilizator nou…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonim"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Încheiați sesiunea pentru invitați"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Semnal pentru date: trei bare."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Semnal pentru date: complet."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet deconectat."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet conectat."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index ac43e49..972dc20 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Создаем нового пользователя…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдоним"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завершить гостевой сеанс"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал передачи данных: три деления."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Надежный сигнал передачи данных."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Устройство отключено от Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Устройство подключено к Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 08fd9a0..0dee8e1 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"නව පරිශීලක තනමින්…"</string>
     <string name="user_nickname" msgid="262624187455825083">"අපනාමය"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"ආරාධිත සැසිය අවසන් කරන්න"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"දත්ත තීරු 3."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"දත්ත සංඥාව පිරී ඇත."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ඊතර්නෙට් විසන්ධි කරන ලදී."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ඊතර්නෙට් සම්බන්ධ කරන ලදී."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ඊතර්නෙට්."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 8f05684..7d49ca3 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Vytvára sa nový používateľ…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Prezývka"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Ukončiť reláciu hosťa"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tri čiarky signálu dátovej siete."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Plný signál dátovej siete."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Sieť ethernet je odpojená"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Sieť ethernet je pripojená"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 3dccf3b..48e5dcc 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Ustvarjanje novega uporabnika …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Končaj sejo gosta"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Podatki s tremi črticami."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Podatkovni signal poln."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernetna povezava je prekinjena."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernetna povezava je vzpostavljena."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c09ad4c..0bc905e 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Po krijohet një përdorues i ri…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Jepi fund sesionit të vizitorit"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Sinjali është me tre vija."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Sinjali i të dhënave është i plotë."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Lidhja e eternetit u shkëput."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Lidhja e eternetit u lidh."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Eternet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index b5a91bf..6d19af8 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -555,7 +555,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Прави се нови корисник…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Надимак"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Заврши сесију госта"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string>
@@ -592,5 +592,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Сигнал за податке од три црте."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Сигнал за податке је најјачи."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Веза са етернетом је прекинута."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Етернет је повезан."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Етернет."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 16a1be6..944c423 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Skapar ny användare …"</string>
     <string name="user_nickname" msgid="262624187455825083">"Smeknamn"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Avsluta gästsession"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data: tre staplar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Datasignalen är full."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet har kopplats från."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet har anslutits."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 58896c8..442f54b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Inaweka mtumiaji mpya…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Jina wakilishi"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Maliza kipindi cha mgeni"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Fito tatu za habari."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Ishara ya data imejaa."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethaneti imeondolewa."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethaneti imeunganishwa."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethaneti."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 71cf7d4..561a122 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"புதிய பயனரை உருவாக்குகிறது…"</string>
     <string name="user_nickname" msgid="262624187455825083">"புனைப்பெயர்"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"விருந்தினர் அமர்வை நிறைவுசெய்"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"தரவு சிக்னல் மூன்று கோட்டில் உள்ளது."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"தரவு சிக்னல் முழுமையாக உள்ளது."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ஈத்தர்நெட் துண்டிக்கப்பட்டது."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ஈத்தர்நெட் இணைக்கப்பட்டது."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ஈதர்நெட்."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c3a65e7..18d7f28 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"కొత్త యూజర్‌ను క్రియేట్ చేస్తోంది…"</string>
     <string name="user_nickname" msgid="262624187455825083">"మారుపేరు"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"గెస్ట్ సెషన్‌ను ముగించు"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్‌ను ఎంచుకోండి"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"డేటా మూడు బార్లు."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"డేటా సిగ్నల్ సంపూర్ణంగా ఉంది."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ఈథర్‌నెట్ డిస్‌కనెక్ట్ చేయబడింది."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ఈథర్‌నెట్ కనెక్ట్ చేయబడింది."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ఈథర్‌నెట్."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 903accb..ceab26c 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"กำลังสร้างผู้ใช้ใหม่…"</string>
     <string name="user_nickname" msgid="262624187455825083">"ชื่อเล่น"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"จบเซสชันผู้เยี่ยมชม"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"สัญญาณข้อมูลสามขีด"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"สัญญาณข้อมูลเต็ม"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ยกเลิกการเชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"เชื่อมต่ออีเทอร์เน็ตแล้ว"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"อีเทอร์เน็ต"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index b259201..671fa14 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Gumagawa ng bagong user…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nickname"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Tapusin ang session ng bisita"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Data na tatlong bar."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Puno ang signal ng data."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Nadiskonekta ang Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Nakakonekta ang Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index a0bc362..98d89f3 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yeni kullanıcı oluşturuluyor…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Takma ad"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Misafir oturumunu sonlandır"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Veri sinyali üç çubuk."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Veri sinyali tam."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet bağlantısı kesildi."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet bağlandı."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 509c8a6..4e739ac 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -556,7 +556,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Створення нового користувача…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Завершити сеанс у режимі \"Гість\""</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Гість"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string>
@@ -593,5 +593,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Три смужки сигналу даних."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Максимальний сигнал даних."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Ethernet відключено."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Ethernet підключено."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index e097ab2..c7dee95 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"نیا صارف تخلیق کرنا…"</string>
     <string name="user_nickname" msgid="262624187455825083">"عرفی نام"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"مہمان سیشن ختم کریں"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"ڈیٹا کے تین بارز۔"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"ڈیٹا سگنل بھرا ہوا ہے۔"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"ایتھرنیٹ منقطع ہے۔"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"ایتھرنیٹ منسلک ہے۔"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"ایتھرنیٹ۔"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 8c2a95d..433096b 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Nik"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Mehmon seansini yakunlash"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Ma’lumotlar uchta panelda."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Internet signali butun."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Qurilma Ethernet tarmog‘idan uzildi."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Qurilma Ethernet tarmog‘iga ulandi."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7a4d89b..a14462b 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Đang tạo người dùng mới…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Biệt hiệu"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Kết thúc phiên khách"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Khách"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Tín hiệu dữ liệu ba vạch."</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Tín hiệu dữ liệu đầy đủ."</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"Đã ngắt kết nối Ethernet."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"Đã kết nối Ethernet."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 7cd61fc..f6bea91 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在创建新用户…"</string>
     <string name="user_nickname" msgid="262624187455825083">"昵称"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"结束访客会话"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"访客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"数据信号强度为三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"数据信号满格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太网已断开连接。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"以太网已连接。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太网。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 458071c..3cf15a9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網絡訊號強度為三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網絡訊號滿格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"以太網連接中斷。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連接以太網。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"以太網絡。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 867ed8b..7cf0297 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"正在建立新使用者…"</string>
     <string name="user_nickname" msgid="262624187455825083">"暱稱"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"結束訪客工作階段"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"數據網路訊號強度三格。"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"數據網路訊號滿格。"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"未連上乙太網路。"</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"已連上乙太網路。"</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"乙太網路。"</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index e64dbd3..d950d58 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -554,7 +554,7 @@
     <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Idala umsebenzisi omusha…"</string>
     <string name="user_nickname" msgid="262624187455825083">"Isiteketiso"</string>
     <string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
-    <string name="guest_exit_guest" msgid="4754204715192830850">"Misa isikhathi sesihambeli"</string>
+    <string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
     <string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string>
     <string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string>
     <string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string>
@@ -591,5 +591,7 @@
     <string name="accessibility_data_three_bars" msgid="2813876214466722413">"Amabha amathathu edatha"</string>
     <string name="accessibility_data_signal_full" msgid="1808301899314382337">"Igcwele i-signal yedatha"</string>
     <string name="accessibility_ethernet_disconnected" msgid="2832501530856497489">"I-Ethernet inqanyuliwe."</string>
-    <string name="accessibility_ethernet_connected" msgid="2093872142317190618">"I-Ethernet ixhunyiwe."</string>
+    <string name="accessibility_ethernet_connected" msgid="6175942685957461563">"I-Ethernet."</string>
+    <!-- no translation found for accessibility_no_calling (3540827068323895748) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7556ace..3866151 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1380,7 +1380,7 @@
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
     <string name="guest_new_guest">Add guest</string>
     <!-- Label for exiting and removing the guest session in the user switcher [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest">End guest session</string>
+    <string name="guest_exit_guest">Remove guest</string>
     <!-- Name for the guest user [CHAR LIMIT=35] -->
     <string name="guest_nickname">Guest</string>
 
@@ -1485,4 +1485,7 @@
     <string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
     <!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ethernet_connected">Ethernet.</string>
+
+    <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_no_calling">No calling.</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
index 45028ff..eff9e74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java
@@ -48,6 +48,8 @@
 
     public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
 
+    public static final int NO_CALLING = R.string.accessibility_no_calling;
+
     public static final int[] ETHERNET_CONNECTION_VALUES = {
         R.string.accessibility_ethernet_disconnected,
         R.string.accessibility_ethernet_connected,
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 927da0a..2b357c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -396,6 +396,28 @@
     }
 
     /**
+     * Check if USB data signaling (except from charging functions) is disabled by the admin.
+     * Only a device owner or a profile owner on an organization-owned managed profile can disable
+     * USB data signaling.
+     *
+     * @return EnforcedAdmin Object containing the enforced admin component and admin user details,
+     * or {@code null} if USB data signaling is not disabled.
+     */
+    public static EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context, int userId) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm == null || dpm.isUsbDataSignalingEnabledForUser(userId)) {
+            return null;
+        } else {
+            EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+            int managedProfileId = getManagedProfileId(context, userId);
+            if (admin == null && managedProfileId != UserHandle.USER_NULL) {
+                admin = getProfileOrDeviceOwner(context, getUserHandleOf(managedProfileId));
+            }
+            return admin;
+        }
+    }
+
+    /**
      * Check if {@param packageName} is restricted by the profile or device owner from using
      * metered data.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index 64cb0f1..43717ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -196,7 +196,7 @@
     }
 
     private void stopTrackingWifiRestart() {
-        mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+        mWifiManager.unregisterSubsystemRestartTrackingCallback(
                 mWifiSubsystemRestartTrackingCallback);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index caabf9a..1474f18 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,10 +16,13 @@
 
 package com.android.settingslib.development;
 
+import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled;
+
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -28,9 +31,9 @@
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
 import androidx.preference.TwoStatePreference;
 
+import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.core.ConfirmationDialogController;
 
 public abstract class AbstractEnableAdbPreferenceController extends
@@ -44,7 +47,7 @@
     public static final int ADB_SETTING_OFF = 0;
 
 
-    protected SwitchPreference mPreference;
+    protected RestrictedSwitchPreference mPreference;
 
     public AbstractEnableAdbPreferenceController(Context context) {
         super(context);
@@ -54,7 +57,7 @@
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
         if (isAvailable()) {
-            mPreference = (SwitchPreference) screen.findPreference(KEY_ENABLE_ADB);
+            mPreference = (RestrictedSwitchPreference) screen.findPreference(KEY_ENABLE_ADB);
         }
     }
 
@@ -77,6 +80,10 @@
     @Override
     public void updateState(Preference preference) {
         ((TwoStatePreference) preference).setChecked(isAdbEnabled());
+        if (isAvailable()) {
+            ((RestrictedSwitchPreference) preference).setDisabledByAdmin(
+                    checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+        }
     }
 
     public void enablePreference(boolean enabled) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 09f285b..14a7cfa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -65,7 +65,7 @@
                 return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
             case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
-            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
+            case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
                 return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
             default:
                 return "unsupported";
@@ -180,7 +180,7 @@
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
                 TelephonyIcons.NR_5G);
         networkToIconLookup.put(toDisplayIconKey(
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
                 TelephonyIcons.NR_5G_PLUS);
         networkToIconLookup.put(toIconKey(
                 TelephonyManager.NETWORK_TYPE_NR),
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 4c7b898..0cd5e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -266,7 +266,7 @@
                                         serviceState.getDataRegState()) + ")")
                                         .append(',')
                 .append("signalStrength=").append(signalStrength == null ? ""
-                        : signalStrength.toString()).append(',')
+                        : signalStrength.getLevel()).append(',')
                 .append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? ""
                         : telephonyDisplayInfo.toString()).append(']').toString();
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index 0cb9906..e3413aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -317,5 +317,21 @@
         ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
         ICON_NAME_TO_ICON.put("notdefaultdata", NOT_DEFAULT_DATA);
     }
+
+    public static final int[] WIFI_CALL_STRENGTH_ICONS = {
+        R.drawable.ic_wifi_call_strength_1,
+        R.drawable.ic_wifi_call_strength_1,
+        R.drawable.ic_wifi_call_strength_2,
+        R.drawable.ic_wifi_call_strength_3,
+        R.drawable.ic_wifi_call_strength_3
+    };
+
+    public static final int[] MOBILE_CALL_STRENGTH_ICONS = {
+        R.drawable.ic_mobile_call_strength_1,
+        R.drawable.ic_mobile_call_strength_1,
+        R.drawable.ic_mobile_call_strength_2,
+        R.drawable.ic_mobile_call_strength_3,
+        R.drawable.ic_mobile_call_strength_3
+    };
 }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 78282fb..841a49e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -39,6 +39,8 @@
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -47,6 +49,8 @@
  * Track status of Wi-Fi for the Sys UI.
  */
 public class WifiStatusTracker {
+    private static final int HISTORY_SIZE = 32;
+    private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
     private final Context mContext;
     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
     private final WifiManager mWifiManager;
@@ -54,6 +58,10 @@
     private final ConnectivityManager mConnectivityManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Set<Integer> mNetworks = new HashSet<>();
+    // Save the previous HISTORY_SIZE states for logging.
+    private final String[] mHistory = new String[HISTORY_SIZE];
+    // Where to copy the next state into.
+    private int mHistoryIndex;
     private final WifiNetworkScoreCache.CacheListener mCacheListener =
             new WifiNetworkScoreCache.CacheListener(mHandler) {
                 @Override
@@ -93,6 +101,13 @@
             } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
             }
+            String log = new StringBuilder()
+                    .append(SSDF.format(System.currentTimeMillis())).append(",")
+                    .append("onCapabilitiesChanged: ")
+                    .append("network=").append(network).append(",")
+                    .append("networkCapabilities=").append(networkCapabilities)
+                    .toString();
+            recordLastWifiNetwork(log);
             if (wifiInfo != null) {
                 updateWifiInfo(wifiInfo);
                 updateStatusLabel();
@@ -102,6 +117,12 @@
 
         @Override
         public void onLost(Network network) {
+            String log = new StringBuilder()
+                    .append(SSDF.format(System.currentTimeMillis())).append(",")
+                    .append("onLost: ")
+                    .append("network=").append(network)
+                    .toString();
+            recordLastWifiNetwork(log);
             if (mNetworks.contains(network.getNetId())) {
                 mNetworks.remove(network.getNetId());
                 updateWifiInfo(null);
@@ -336,4 +357,25 @@
         }
         return null;
     }
+
+    private void recordLastWifiNetwork(String log) {
+        mHistory[mHistoryIndex] = log;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
+    }
+
+    /** Dump function. */
+    public void dump(PrintWriter pw) {
+        pw.println("  - WiFi Network History ------");
+        int size = 0;
+        for (int i = 0; i < HISTORY_SIZE; i++) {
+            if (mHistory[i] != null) size++;
+        }
+        // Print out the previous states in ordered number.
+        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+            pw.println("  Previous WiFiNetwork("
+                    + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+                    + mHistory[i & (HISTORY_SIZE - 1)]);
+        }
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index e84a25c..5f53a92c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -19,18 +19,24 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
+
+import com.android.settingslib.RestrictedSwitchPreference;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,26 +49,34 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class EnableAdbPreferenceControllerTest {
+
+    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("test", "test");
+
     @Mock(answer = RETURNS_DEEP_STUBS)
     private PreferenceScreen mScreen;
     @Mock
     private UserManager mUserManager;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
 
     private Context mContext;
-    private SwitchPreference mPreference;
+    private RestrictedSwitchPreference mPreference;
     private ConcreteEnableAdbPreferenceController mController;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         ShadowApplication shadowContext = ShadowApplication.getInstance();
         shadowContext.setSystemService(Context.USER_SERVICE, mUserManager);
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDevicePolicyManager);
+        doReturn(mContext).when(mContext).createPackageContextAsUser(
+                any(String.class), anyInt(), any(UserHandle.class));
         mController = new ConcreteEnableAdbPreferenceController(mContext);
-        mPreference = new SwitchPreference(mContext);
+        mPreference = new RestrictedSwitchPreference(mContext);
         mPreference.setKey(mController.getPreferenceKey());
         when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
     }
@@ -125,6 +139,9 @@
     @Test
     public void updateState_settingsOn_shouldCheck() {
         when(mUserManager.isAdminUser()).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
+        when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
+                UserHandle.myUserId())).thenReturn(true);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.ADB_ENABLED, 1);
         mPreference.setChecked(false);
@@ -138,6 +155,9 @@
     @Test
     public void updateState_settingsOff_shouldUncheck() {
         when(mUserManager.isAdminUser()).thenReturn(true);
+        when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME);
+        when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser(
+                UserHandle.myUserId())).thenReturn(true);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.ADB_ENABLED, 0);
         mPreference.setChecked(true);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 84dacfd..d10ff40 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -251,5 +251,5 @@
     <integer name="def_accessibility_magnification_capabilities">3</integer>
 
     <!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
-    <bool name="def_enable_non_resizable_multi_window">false</bool>
+    <bool name="def_enable_non_resizable_multi_window">true</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a6e2af9..9cd7083 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -24,6 +24,7 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
@@ -212,7 +213,6 @@
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) throws IOException {
-
         byte[] systemSettingsData = getSystemSettings();
         byte[] secureSettingsData = getSecureSettings();
         byte[] globalSettingsData = getGlobalSettings();
@@ -1204,17 +1204,25 @@
     }
 
     private byte[] getSimSpecificSettingsData() {
-        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
-        byte[] simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
-        Log.i(TAG, "sim specific data of length + " + simSpecificData.length
+        byte[] simSpecificData = new byte[0];
+        PackageManager packageManager = getBaseContext().getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+            simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
+            Log.i(TAG, "sim specific data of length + " + simSpecificData.length
                 + " successfully retrieved");
+        }
 
         return simSpecificData;
     }
 
     private void restoreSimSpecificSettings(byte[] data) {
-        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
-        subManager.restoreAllSimSpecificSettingsFromBackup(data);
+        PackageManager packageManager = getBaseContext().getPackageManager();
+        boolean hasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+        if (hasTelephony) {
+            SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+            subManager.restoreAllSimSpecificSettingsFromBackup(data);
+        }
     }
 
     private void updateWindowManagerIfNeeded(Integer previousDensity) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 438cec8..6719f17 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -323,6 +323,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE,
                     Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
                     Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
                     Settings.Global.LOCK_SOUND,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eab0990..539a81b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -87,6 +87,7 @@
     <!--  TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
     <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
+    <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
     <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
@@ -99,6 +100,7 @@
     <uses-permission android:name="android.permission.REBOOT" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.POWER_SAVER" />
+    <uses-permission android:name="android.permission.BATTERY_PREDICTION" />
     <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
     <uses-permission android:name="android.permission.BACKUP" />
     <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
@@ -399,6 +401,10 @@
     <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+    <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" />
+
+    <!-- Permission required for CTS test - CtsRebootReadinessTestCases -->
+    <uses-permission android:name="android.permission.SIGNAL_REBOOT_READINESS" />
 
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 6ba1fcb..34901f5 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@
 svetoslavganov@google.com
 hackbod@google.com
 yamasani@google.com
-moltmann@google.com
 toddke@google.com
 cbrubaker@google.com
 omakoto@google.com
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7bfb42b..bf5198e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -194,6 +194,5 @@
     dxflags: ["--multi-dex"],
     required: [
         "privapp_whitelist_com.android.systemui",
-        "checked-wm_shell_protolog.json",
     ],
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5af0244..2faca8d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -600,6 +600,14 @@
             android:permission="android.permission.BIND_REMOTEVIEWS"
             android:exported="false" />
 
+        <!-- ContentProvider that returns a People Tile preview for a given shortcut -->
+        <provider
+            android:name="com.android.systemui.people.PeopleProvider"
+            android:authorities="com.android.systemui.people.PeopleProvider"
+            android:exported="true"
+            android:permission="android.permission.GET_PEOPLE_TILE_PREVIEW">
+        </provider>
+
         <!-- a gallery of delicious treats -->
         <service
             android:name=".DessertCaseDream"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
index 0831e0e..da079cf0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -103,5 +103,10 @@
         default Animator getOutAnimation() {
             return null;
         }
+
+        /**
+         * Called on orientation changes.
+         */
+        default void onOrientationChange(int orientation) {  }
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index d43aaf0..beee03b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -46,6 +46,21 @@
         return true;
     }
 
+    /**
+     * @return if detail panel should animate when shown or closed
+     */
+    default boolean shouldAnimate() {
+        return true;
+    }
+
+    /**
+     * @return true if the callback handled the event and wants to keep the detail panel open, false
+     * otherwise. Returning false will close the panel.
+     */
+    default boolean onDoneButtonClicked() {
+        return false;
+    }
+
     default UiEventLogger.UiEventEnum openDetailEvent() {
         return INVALID;
     }
diff --git a/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
new file mode 100644
index 0000000..51c442a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ripple_drawable_pin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:attr/colorControlHighlight"
+        android:radius="40dp"/>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 35a2195..f3ec39d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -74,9 +74,7 @@
             android:layout_gravity="center_horizontal"
             android:gravity="center_horizontal"
             android:textSize="100dp"
-            android:includeFontPadding="false"
             android:fontFamily="@font/clock"
-            android:lineSpacingMultiplier=".7"
             android:typeface="monospace"
             android:elegantTextHeight="false"
             dozeWeight="200"
@@ -96,8 +94,6 @@
             android:layout_gravity="center_horizontal"
             android:gravity="center_horizontal"
             android:textSize="@dimen/large_clock_text_size"
-            android:includeFontPadding="false"
-            android:lineSpacingMultiplier=".7"
             android:fontFamily="@font/clock"
             android:typeface="monospace"
             android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res/color/kg_user_avatar_frame.xml b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
new file mode 100644
index 0000000..174981e
--- /dev/null
+++ b/packages/SystemUI/res/color/kg_user_avatar_frame.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ 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
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:color="@color/kg_user_switcher_avatar_background" />
+    <item android:color="@color/kg_user_switcher_avatar_background" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/end_guest_button_background.xml b/packages/SystemUI/res/drawable/end_guest_button_background.xml
new file mode 100644
index 0000000..5644b65
--- /dev/null
+++ b/packages/SystemUI/res/drawable/end_guest_button_background.xml
@@ -0,0 +1,25 @@
+<?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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <stroke
+        android:width="@dimen/end_guest_button_border_size"
+        android:color="?android:attr/colorControlHighlight" />
+    <corners android:radius="@dimen/end_guest_button_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/kg_bg_avatar.xml b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
new file mode 100644
index 0000000..addb3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/kg_bg_avatar.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="100"
+        android:viewportHeight="100">
+
+    <path
+        android:fillColor="@color/kg_user_switcher_avatar_background"
+        android:pathData="M50,50m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 827cf4a..109442d 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,8 +16,6 @@
 -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#242424" /> <!-- 14% of white -->
-    <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
-        android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" />
+    <solid android:color="#FFFFFF" />
     <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/toast_background.xml b/packages/SystemUI/res/drawable/toast_background.xml
new file mode 100644
index 0000000..5c45e83
--- /dev/null
+++ b/packages/SystemUI/res/drawable/toast_background.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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#FFFFFFFF" />
+    <corners android:radius="@dimen/toast_bg_radius" />
+</shape>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
new file mode 100644
index 0000000..3938b73
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -0,0 +1,32 @@
+<?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
+  -->
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.phone.UserAvatarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_qs_user_switch_view"
+    android:layout_width="@dimen/kg_framed_avatar_size"
+    android:layout_height="@dimen/kg_framed_avatar_size"
+    android:layout_centerHorizontal="true"
+    android:layout_gravity="center_horizontal|bottom"
+    systemui:avatarPadding="0dp"
+    systemui:badgeDiameter="18dp"
+    systemui:badgeMargin="1dp"
+    systemui:frameColor="@color/kg_user_avatar_frame"
+    systemui:framePadding="0dp"
+    systemui:frameWidth="0dp">
+</com.android.systemui.statusbar.phone.UserAvatarView>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 416ee81..2789ed1 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -43,17 +43,12 @@
             <include layout="@layout/system_icons" />
         </FrameLayout>
 
-        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
-            android:layout_width="@dimen/multi_user_switch_width_keyguard"
-            android:layout_height="match_parent"
-            android:background="@drawable/ripple_drawable"
-            android:layout_marginEnd="@dimen/multi_user_switch_keyguard_margin">
-            <ImageView android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_height="@dimen/multi_user_avatar_keyguard_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside"/>
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+        <ImageView android:id="@+id/multi_user_avatar"
+            android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+            android:layout_gravity="center"
+            android:scaleType="centerInside"/>
     </LinearLayout>
 
     <Space
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
index 983ba6d..253c03e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
@@ -14,10 +14,50 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
-        class="com.android.systemui.statusbar.policy.KeyguardUserSwitcher$Container"
-        android:visibility="gone"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent">
-    <!-- KeyguardUserSwitcher loads keyguard_user_switcher_inner.xml here -->
-</view>
\ No newline at end of file
+<!-- This is a view that shows a user switcher in Keyguard. -->
+<com.android.systemui.statusbar.policy.KeyguardUserSwitcherView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_user_switcher_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="end">
+
+    <com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView
+        android:id="@+id/keyguard_user_switcher_list"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="top|end"
+        android:gravity="end" />
+
+    <LinearLayout
+        android:id="@+id/end_guest_button"
+        android:layout_height="@dimen/end_guest_button_layout_height"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/end_guest_button_margin_bottom"
+        android:orientation="horizontal"
+        android:gravity="center"
+        android:paddingLeft="@dimen/end_guest_button_padding_horizontal"
+        android:paddingRight="@dimen/end_guest_button_padding_horizontal"
+        android:background="@drawable/end_guest_button_background"
+        android:visibility="gone">
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:src="@drawable/ic_exit_to_app"
+            android:background="@android:color/transparent"
+            android:color="?attr/wallpaperTextColor" />
+        <TextView
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            android:textColor="?attr/wallpaperTextColor"
+            android:textSize="13sp"
+            android:text="@string/guest_exit_button" />
+    </LinearLayout>
+
+</com.android.systemui.statusbar.policy.KeyguardUserSwitcherView>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
deleted file mode 100644
index 4c1042e..0000000
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_inner.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2016 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ 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.keyguard.AlphaOptimizedLinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/keyguard_user_switcher_inner"
-    android:orientation="vertical"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content"
-    android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
-    android:layout_gravity="end"
-    android:gravity="end"
-    android:paddingTop="4dp">
-</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 1cd1a04..aaa372a 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -19,29 +19,30 @@
 <!-- LinearLayout -->
 <com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        xmlns:systemui="http://schemas.android.com/apk/res-auto"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:padding="8dp"
         android:layout_marginEnd="8dp"
-        android:gravity="center_vertical"
+        android:gravity="end|center_vertical"
         android:clickable="true"
-        android:background="@drawable/ripple_drawable"
-        sysui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-        sysui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
-    <TextView android:id="@+id/user_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="13dp"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
-            />
-    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/user_picture"
-            android:layout_width="@dimen/kg_framed_avatar_size"
-            android:layout_height="@dimen/kg_framed_avatar_size"
-            android:contentDescription="@null"
-            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
-            sysui:framePadding="2.5dp"
-            sysui:badgeDiameter="18dp"
-            sysui:badgeMargin="1dp"
-            sysui:frameColor="@color/kg_user_switcher_rounded_background_color" />
+        android:background="@drawable/kg_user_switcher_rounded_bg"
+        systemui:activatedTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher"
+        systemui:regularTextAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher">
+    <TextView
+        android:id="@+id/user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="16dp" />
+    <com.android.systemui.statusbar.phone.UserAvatarView
+        android:id="@+id/user_picture"
+        android:layout_width="@dimen/kg_framed_avatar_size"
+        android:layout_height="@dimen/kg_framed_avatar_size"
+        systemui:avatarPadding="0dp"
+        systemui:badgeDiameter="18dp"
+        systemui:badgeMargin="1dp"
+        systemui:frameWidth="0dp"
+        systemui:framePadding="0dp"
+        systemui:frameColor="@color/kg_user_avatar_frame" />
 </com.android.systemui.statusbar.policy.KeyguardUserDetailItemView>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 8a47a22..95cee66 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -47,7 +47,7 @@
         android:layout_width="wrap_content"
         android:layout_height="48dp"
         android:layout_marginBottom="4dp"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:forceHasOverlappingRendering="false"
     />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 6b42705..a4cf5ed 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -48,7 +48,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textColor="@color/media_primary_text"
+            android:textColor="?android:attr/textColorPrimary"
             android:gravity="start"
             android:textSize="14sp" />
 
@@ -58,7 +58,7 @@
             android:layout_height="wrap_content"
             android:layout_alignParentEnd="true"
             android:fontFamily="@*android:string/config_bodyFontFamily"
-            android:textColor="@color/media_primary_text"
+            android:textColor="?android:attr/textColorPrimary"
             android:gravity="end"
             android:textSize="14sp" />
     </FrameLayout>
@@ -120,13 +120,13 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        android:gravity="center_vertical|end"
+        android:gravity="center"
+        android:background="@drawable/qs_media_light_source"
         android:forceHasOverlappingRendering="false">
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:foreground="@drawable/qs_media_seamless_background"
-            android:background="@drawable/qs_media_light_source"
+            android:background="@drawable/qs_media_seamless_background"
             android:orientation="horizontal"
             android:padding="6dp"
             android:contentDescription="@string/quick_settings_media_device_label">
@@ -135,7 +135,7 @@
                 android:layout_width="@dimen/qs_seamless_icon_size"
                 android:layout_height="@dimen/qs_seamless_icon_size"
                 android:layout_gravity="center"
-                android:tint="@color/media_primary_text"
+                android:tint="?android:attr/colorPrimary"
                 android:src="@*android:drawable/ic_media_seamless" />
             <TextView
                 android:visibility="gone"
@@ -147,7 +147,7 @@
                 android:fontFamily="@*android:string/config_headlineFontFamily"
                 android:singleLine="true"
                 android:text="@*android:string/ext_media_seamless_action"
-                android:textColor="@color/media_primary_text"
+                android:textColor="?android:attr/colorPrimary"
                 android:textDirection="locale"
                 android:textSize="14sp" />
         </LinearLayout>
@@ -157,7 +157,7 @@
         android:id="@+id/media_seamless_fallback"
         android:layout_width="@dimen/qs_seamless_icon_size"
         android:layout_height="@dimen/qs_seamless_icon_size"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/ic_cast_connected"
         android:forceHasOverlappingRendering="false" />
 
@@ -171,15 +171,15 @@
         android:clickable="true"
         android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
         android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding"
-        android:thumbTint="@color/media_primary_text"
-        android:progressTint="@color/media_seekbar_progress"
-        android:progressBackgroundTint="@color/media_disabled"
+        android:thumbTint="?android:attr/textColorPrimary"
+        android:progressTint="?android:attr/textColorPrimary"
+        android:progressBackgroundTint="?android:attr/colorBackground"
         android:splitTrack="false" />
 
     <!-- App name -->
     <TextView
         android:id="@+id/app_name"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:singleLine="true"
@@ -194,7 +194,7 @@
         android:layout_height="wrap_content"
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         android:singleLine="true"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:textSize="16sp" />
 
     <!-- Artist name -->
@@ -204,12 +204,12 @@
         android:layout_height="wrap_content"
         android:fontFamily="@*android:string/config_headlineFontFamily"
         android:singleLine="true"
-        android:textColor="@color/media_secondary_text"
+        android:textColor="?android:attr/textColorSecondary"
         android:textSize="14sp" />
 
     <com.android.internal.widget.CachingIconView
         android:id="@+id/icon"
-        android:tint="@color/media_primary_text"
+        android:tint="?android:attr/textColorPrimary"
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_margin="6dp" />
@@ -223,7 +223,7 @@
         android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
         android:id="@+id/media_text"
         android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorSecondary"
         android:text="@string/controls_media_title"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
@@ -238,7 +238,7 @@
         android:id="@+id/remove_text"
         android:fontFamily="@*android:string/config_headlineFontFamily"
         android:singleLine="true"
-        android:textColor="@color/media_primary_text"
+        android:textColor="?android:attr/textColorPrimary"
         android:text="@string/controls_media_close_session"
         app:layout_constraintTop_toBottomOf="@id/media_text"
         app:layout_constraintStart_toStartOf="parent"
@@ -262,7 +262,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/controls_media_settings_button" />
     </FrameLayout>
 
@@ -283,7 +283,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/cancel" />
     </FrameLayout>
 
@@ -304,7 +304,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-            android:textColor="@android:color/white"
+            android:textColor="?android:attr/textColorPrimary"
             android:text="@string/controls_media_dismiss_button"
         />
     </FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 3c30632..bad5826 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -22,19 +22,14 @@
     android:layout_height="match_parent"
     android:layout_width="wrap_content"
     android:layout_gravity="center_vertical|end"
-    android:focusable="true" >
+    android:focusable="true"
+    android:minWidth="48dp" >
 
-        <FrameLayout
-            android:id="@+id/background"
+        <LinearLayout
+            android:id="@+id/icons_container"
             android:layout_height="@dimen/ongoing_appops_chip_height"
             android:layout_width="wrap_content"
-            android:minWidth="48dp"
-            android:layout_gravity="center_vertical">
-                <LinearLayout
-                    android:id="@+id/icons_container"
-                    android:layout_height="match_parent"
-                    android:layout_width="wrap_content"
-                    android:gravity="center_vertical"
-                    />
-          </FrameLayout>
+            android:gravity="center_vertical"
+            android:layout_gravity="center"
+            />
 </com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d6385ff..a8ef7c3 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -31,6 +31,18 @@
         android:layout_height="match_parent"
         android:visibility="gone" />
 
+    <ViewStub
+        android:id="@+id/keyguard_qs_user_switch_stub"
+        android:layout="@layout/keyguard_qs_user_switch"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher_stub"
+        android:layout="@layout/keyguard_user_switcher"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent" />
+
     <include
         layout="@layout/keyguard_status_view"
         android:visibility="gone" />
@@ -57,6 +69,13 @@
             systemui:layout_constraintEnd_toEndOf="parent"
         />
 
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/qs_edge_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            systemui:layout_constraintGuide_percent="0.5"
+            android:orientation="vertical"/>
+
         <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
             android:layout_marginTop="@dimen/notification_panel_margin_top"
@@ -72,12 +91,6 @@
 
         <include layout="@layout/photo_preview_overlay" />
 
-        <ViewStub
-            android:id="@+id/keyguard_user_switcher"
-            android:layout="@layout/keyguard_user_switcher"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent" />
-
         <include
             layout="@layout/keyguard_status_bar"
             android:visibility="invisible" />
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
new file mode 100644
index 0000000..de4e062
--- /dev/null
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:maxWidth="@dimen/toast_width"
+    android:orientation="horizontal"
+    android:background="@drawable/toast_background"
+    android:backgroundTint="?android:attr/colorBackground"
+    android:layout_marginEnd="16dp"
+    android:layout_marginStart="16dp"
+    android:gravity="center_vertical">
+
+    <!-- Icon should be 24x24, make slightly larger to allow for shadowing, adjust via padding -->
+    <ImageView
+        android:id="@+id/icon"
+        android:alpha="@dimen/toast_icon_alpha"
+        android:padding="11.5dp"
+        android:layout_width="@dimen/toast_icon_size"
+        android:layout_height="@dimen/toast_icon_size"/>
+    <TextView
+        android:id="@+id/text"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:paddingStart="0dp"
+        android:paddingEnd="22dp"
+        android:textSize="@dimen/toast_text_size"
+        android:textColor="?android:attr/textColorPrimary"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..24c7655 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -52,4 +52,6 @@
     <!-- (footer_height -48dp)/2 -->
     <dimen name="controls_management_footer_top_margin">4dp</dimen>
     <dimen name="controls_management_favorites_top_margin">8dp</dimen>
+
+    <dimen name="toast_y_offset">24dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 3153d0d..37ec576 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -89,6 +89,8 @@
     <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in keyguard user switcher -->
     <color name="kg_user_switcher_selected_avatar_icon_color">#202124</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">#3C4043</color>
     <!-- Icon color for user avatars in quick settings user switcher  -->
     <color name="qs_user_switcher_avatar_icon_color">@android:color/background_light</color>
     <!-- Icon color for selected user avatars in quick settings user switcher  -->
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 02bd602..ee2b82d 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -23,13 +23,6 @@
         <item name="numColumns">4</item>
     </style>
 
-    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
-        <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
-        <item name="android:textStyle">normal</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?attr/wallpaperTextColor</item>
-    </style>
-
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8166e35..a1191ae 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -139,6 +139,10 @@
     <!-- Size of shadows/elevations on keyguard -->
     <attr name="shadowRadius" format="float" />
 
+    <attr name="handleThickness" format="dimension" />
+    <attr name="handleColor" format="color" />
+    <attr name="scrimColor" format="color" />
+
     <!-- Used display CarrierText in Keyguard or QS Footer -->
     <declare-styleable name="CarrierText">
         <attr name="allCaps" format="boolean" />
@@ -173,15 +177,15 @@
     </declare-styleable>
 
     <declare-styleable name="CropView">
-        <attr name="handleThickness" format="dimension" />
-        <attr name="handleColor" format="color" />
-        <attr name="scrimColor" format="color" />
+        <attr name="handleThickness" />
+        <attr name="handleColor" />
+        <attr name="scrimColor" />
     </declare-styleable>
 
     <declare-styleable name="MagnifierView">
-        <attr name="handleThickness" format="dimension" />
-        <attr name="handleColor" format="color" />
-        <attr name="scrimColor" format="color" />
+        <attr name="handleThickness" />
+        <attr name="handleColor" />
+        <attr name="scrimColor" />
         <attr name="borderThickness" format="dimension" />
         <attr name="borderColor" format="color" />
     </declare-styleable>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5fb6de7..acd671c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -66,9 +66,13 @@
     <!-- Color for rounded background for activated user in keyguard user switcher -->
     <color name="kg_user_switcher_activated_background_color">#26000000</color>
     <!-- Icon color for user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_avatar_icon_color">@android:color/background_light</color>
-    <!-- Icon color for selected user avatars in keyguard user switcher -->
-    <color name="kg_user_switcher_selected_avatar_icon_color">@android:color/background_light</color>
+    <color name="kg_user_switcher_avatar_icon_color">@color/GM2_grey_800</color>
+    <!-- Icon color for user avatars in keyguard user switcher that restricted
+         (e.g. cannot be switched to) -->
+    <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
+    <!-- Color of background circle of user avatars in keyguard user switcher -->
+    <color name="kg_user_switcher_avatar_background">@color/GM2_grey_300</color>
+
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
     <!-- Icon color for selected user avatars in user switcher quick settings -->
@@ -240,11 +244,8 @@
     <color name="magnification_switch_button_color">#7F000000</color>
 
     <!-- media -->
-    <color name="media_primary_text">@android:color/white</color>
-    <color name="media_secondary_text">#99ffffff</color> <!-- 60% -->
-    <color name="media_seekbar_progress">#c0ffffff</color>
     <color name="media_disabled">#80ffffff</color>
-    <color name="media_seamless_border">#26ffffff</color> <!-- 15% -->
+    <color name="media_seamless_border">?android:attr/colorAccent</color>
     <color name="media_divider">#1d000000</color>
 
     <!-- controls -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb04c3b..78927f8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -107,7 +107,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls
     </string>
 
     <!-- The tiles to display in QuickSettings -->
@@ -279,6 +279,11 @@
     <!-- Whether to show the full screen user switcher. -->
     <bool name="config_enableFullscreenUserSwitcher">false</bool>
 
+    <!-- Whether the multi-user switch on the keyguard opens QS user panel. If false, clicking the
+         user switch on the keyguard will replace the notifications and status area with the user
+         switcher. The multi-user switch is only shown if config_keyguardUserSwitcher=false. -->
+    <bool name="config_keyguard_user_switch_opens_qs_details">false</bool>
+
     <!-- SystemUIFactory component -->
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5bb46a8..0d92aea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -163,10 +163,14 @@
     <!-- heads up elevation that is added if the view is pinned -->
     <dimen name="heads_up_pinned_elevation">16dp</dimen>
 
-    <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+    <!-- Height of a messaging notifications with actions at least. Note that this is an upper bound
          and the notification won't use this much, but is measured with wrap_content -->
     <dimen name="notification_messaging_actions_min_height">196dp</dimen>
 
+    <!-- Height of a call notification. Note that this is an upper bound
+     and the notification won't use this much, but is measured with wrap_content -->
+    <dimen name="call_notification_full_height">172dp</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
@@ -688,6 +692,11 @@
     <!-- The amount to shift the clocks during a small/large transition -->
     <dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
 
+    <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+    <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+    <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
     <item name="scrim_behind_alpha" format="float" type="dimen">0.62</item>
 
     <!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
@@ -734,9 +743,6 @@
     <!-- end margin for system icons if multi user switch is hidden -->
     <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
 
-    <!-- The thickness of the colored border around the current user. -->
-    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
-
     <dimen name="data_usage_graph_marker_width">4dp</dimen>
 
     <!-- The padding bottom of the clock group when QS is expanded. -->
@@ -796,7 +802,7 @@
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">54dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
-    <dimen name="kg_framed_avatar_size">54dp</dimen>
+    <dimen name="kg_framed_avatar_size">48dp</dimen>
 
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">16dp</dimen>
@@ -1170,17 +1176,13 @@
     <dimen name="display_cutout_margin_consumption">0px</dimen>
 
     <!-- Height of the Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_height">32dp</dimen>
-    <!-- Padding between background of Ongoing App Ops chip and content -->
-    <dimen name="ongoing_appops_chip_bg_padding">8dp</dimen>
+    <dimen name="ongoing_appops_chip_height">24dp</dimen>
     <!-- Side padding between background of Ongoing App Ops chip and content -->
     <dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
-    <!-- Margin between icons of Ongoing App Ops chip when QQS-->
-    <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen>
-    <!-- Margin between icons of Ongoing App Ops chip when QS-->
-    <dimen name="ongoing_appops_chip_icon_margin_expanded">2dp</dimen>
+    <!-- Margin between icons of Ongoing App Ops chip -->
+    <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
 
@@ -1319,8 +1321,16 @@
     <dimen name="screenrecord_status_icon_height">17.5dp</dimen>
     <dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
 
+    <!-- Keyguard user switcher -->
     <dimen name="kg_user_switcher_text_size">16sp</dimen>
 
+    <!-- End guest session button -->
+    <dimen name="end_guest_button_layout_height">32dp</dimen>
+    <dimen name="end_guest_button_padding_horizontal">16dp</dimen>
+    <dimen name="end_guest_button_margin_bottom">96dp</dimen>
+    <dimen name="end_guest_button_border_size">1dp</dimen>
+    <dimen name="end_guest_button_corner_radius">16dp</dimen>
+
     <!-- Opacity at which the background for the shutdown UI will be drawn. -->
     <item name="shutdown_scrim_behind_alpha" format="float" type="dimen">0.95</item>
 
@@ -1349,4 +1359,11 @@
     <dimen name="rounded_slider_icon_size">24dp</dimen>
     <!-- rounded_slider_icon_size / 2 -->
     <dimen name="rounded_slider_icon_inset">12dp</dimen>
+
+    <dimen name="toast_width">296dp</dimen>
+    <item name="toast_icon_alpha" format="float" type="dimen">1</item>
+    <dimen name="toast_text_size">14sp</dimen>
+    <dimen name="toast_y_offset">48dp</dimen>
+    <dimen name="toast_icon_size">48dp</dimen>
+    <dimen name="toast_bg_radius">28dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index ded8a2e..d4bb128 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,11 +18,12 @@
 <resources>
     <bool name="are_flags_overrideable">false</bool>
 
-    <bool name="flag_notification_pipeline2">false</bool>
+    <bool name="flag_notification_pipeline2">true</bool>
     <bool name="flag_notification_pipeline2_rendering">false</bool>
     <bool name="flag_notif_updates">false</bool>
 
     <bool name="flag_shade_is_opaque">false</bool>
+    <bool name="flag_monet">false</bool>
 
     <!-- b/171917882 -->
     <bool name="flag_notification_twocolumn">false</bool>
@@ -34,9 +35,11 @@
 
     <bool name="flag_brightness_slider">false</bool>
 
+    <!-- People Tile flag -->
+    <bool name="flag_conversations">false</bool>
+
     <!-- The new animations to/from lockscreen and AOD! -->
     <bool name="flag_lockscreen_animations">false</bool>
 
-    <!-- People Tile flag -->
-    <bool name="flag_conversations">false</bool>
+    <bool name="flag_toast_style">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index abcf4e8..d997ca2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1112,14 +1112,17 @@
     <!-- Name for a freshly added user [CHAR LIMIT=30] -->
     <string name="user_new_user_name">New user</string>
 
+    <!-- Label for button that exits guest session and clears the guest user data [CHAR LIMIT=50]-->
+    <string name="guest_exit_button">End guest session</string>
+
     <!-- Title of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
-    <string name="guest_exit_guest_dialog_title">End guest session?</string>
+    <string name="guest_exit_guest_dialog_title">Remove guest?</string>
 
     <!-- Message of the confirmation dialog when exiting guest session [CHAR LIMIT=NONE] -->
     <string name="guest_exit_guest_dialog_message">All apps and data in this session will be deleted.</string>
 
     <!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
-    <string name="guest_exit_guest_dialog_remove">End session</string>
+    <string name="guest_exit_guest_dialog_remove">Remove</string>
 
     <!-- Title of the notification when resuming an existing guest session [CHAR LIMIT=NONE] -->
     <string name="guest_wipe_session_title">Welcome back, guest!</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c72548..14b376a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -114,12 +114,12 @@
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
         <item name="android:textSize">@dimen/kg_user_switcher_text_size</item>
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?attr/wallpaperTextColor</item>
     </style>
 
     <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.Activated">
         <item name="android:fontWeight">700</item>
-        <item name="android:textStyle">bold</item>
     </style>
 
     <style name="TextAppearance" />
@@ -582,7 +582,7 @@
 
     <style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
         <item name="android:background">@drawable/qs_media_light_source</item>
-        <item name="android:tint">@android:color/white</item>
+        <item name="android:tint">?android:attr/textColorPrimary</item>
         <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
     </style>
 
@@ -764,6 +764,7 @@
     <style name="TextAppearance.PrivacyDialog">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="UdfpsProgressBarStyle"
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index f834d6d..f83e3a1 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -36,25 +36,23 @@
         app:layout_constraintTop_toTopOf="@id/icon"
         app:layout_constraintBottom_toBottomOf="@id/icon"
         app:layout_constraintStart_toEndOf="@id/icon"
-        app:layout_constraintEnd_toStartOf="@id/media_seamless"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
         app:layout_constrainedWidth="true"
         app:layout_constraintHorizontal_bias="0"
         />
 
     <Constraint
         android:id="@+id/media_seamless"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/app_name"
+        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
         app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintHorizontal_bias="1"
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="48dp"
         app:layout_constraintHeight_min="48dp"
-        android:layout_marginEnd="@dimen/qs_center_guideline_padding"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         />
 
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index d89e0eb..7c67720 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -36,25 +36,23 @@
         app:layout_constraintTop_toTopOf="@id/icon"
         app:layout_constraintBottom_toBottomOf="@id/icon"
         app:layout_constraintStart_toEndOf="@id/icon"
-        app:layout_constraintEnd_toStartOf="@id/media_seamless"
-        app:layout_constraintHorizontal_chainStyle="spread_inside"
+        app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
         app:layout_constrainedWidth="true"
         app:layout_constraintHorizontal_bias="0"
         />
 
     <Constraint
         android:id="@+id/media_seamless"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/app_name"
+        app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
         app:layout_constraintHorizontal_chainStyle="spread_inside"
         app:layout_constraintHorizontal_bias="1"
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="48dp"
         app:layout_constraintHeight_min="48dp"
-        android:layout_marginEnd="@dimen/qs_center_guideline_padding"
         android:layout_marginStart="@dimen/qs_center_guideline_padding"
         />
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7f04f28..72e4061 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -32,8 +32,29 @@
     private final Matrix mTmpTransform = new Matrix();
     private final float[] mTmpFloat9 = new float[9];
     private final RectF mTmpSourceRectF = new RectF();
+    private final RectF mTmpDestinationRectF = new RectF();
     private final Rect mTmpDestinationRect = new Rect();
 
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+    }
+
+    public void scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+            Rect sourceBounds, Rect destinationBounds,
+            float degree, float positionX, float positionY) {
+        mTmpSourceRectF.set(sourceBounds);
+        mTmpDestinationRectF.set(destinationBounds);
+        mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        mTmpTransform.postRotate(degree, 0, 0);
+        tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+                .setPosition(leash, positionX, positionY);
+    }
+
     public void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds, Rect insets) {
         mTmpSourceRectF.set(sourceBounds);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e38cf23..5c943f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -245,9 +245,11 @@
     void setSideStageVisibility(in boolean visible) = 36;
     /** Removes the split-screen stages. */
     void exitSplitScreen() = 37;
-    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 38;
+    /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */
+    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38;
+    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39;
     void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
-            in Bundle options, in UserHandle user) = 39;
+            in Bundle options, in UserHandle user) = 40;
     void startIntent(
-            in PendingIntent intent, in int stage, in int position, in Bundle options) = 40;
+            in PendingIntent intent, in int stage, in int position, in Bundle options) = 41;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 186379a..f6b239e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -43,17 +43,6 @@
 
     public static final String TAG = "Task";
 
-    /* Task callbacks */
-    @Deprecated
-    public interface TaskCallbacks {
-        /* Notifies when a task has been bound */
-        void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
-        /* Notifies when a task has been unbound */
-        void onTaskDataUnloaded();
-        /* Notifies when a task's windowing mode has changed. */
-        void onTaskWindowingModeChanged();
-    }
-
     /**
      * The Task Key represents the unique primary key for the task
      */
@@ -209,12 +198,6 @@
     public TaskKey key;
 
     /**
-     * The temporary sort index in the stack, used when ordering the stack.
-     */
-    @Deprecated
-    public int temporarySortIndexInStack;
-
-    /**
      * The icon is the task description icon (if provided), which falls back to the activity icon,
      * which can then fall back to the application icon.
      */
@@ -229,45 +212,24 @@
     public int colorPrimary;
     @ViewDebug.ExportedProperty(category="recents")
     public int colorBackground;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean useLightOnPrimaryColor;
 
     /**
      * The task description for this task, only used to reload task icons.
      */
     public TaskDescription taskDescription;
 
-    /**
-     * The state isLaunchTarget will be set for the correct task upon launching Recents.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isLaunchTarget;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isStackTask;
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public boolean isSystemApp;
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isDockable;
 
-    /**
-     * Resize mode. See {@link ActivityInfo#resizeMode}.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    @Deprecated
-    public int resizeMode;
-
     @ViewDebug.ExportedProperty(category="recents")
     public ComponentName topActivity;
 
     @ViewDebug.ExportedProperty(category="recents")
     public boolean isLocked;
 
-    @Deprecated
-    private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
+    // Last snapshot data, only used for recent tasks
+    public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+            new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
 
     public Task() {
         // Do nothing
@@ -289,6 +251,15 @@
         this.taskDescription = new TaskDescription();
     }
 
+    public Task(Task other) {
+        this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
+                other.isLocked, other.taskDescription, other.topActivity);
+    }
+
+    /**
+     * Use {@link Task#Task(Task)}.
+     */
+    @Deprecated
     public Task(TaskKey key, int colorPrimary, int colorBackground,
             boolean isDockable, boolean isLocked, TaskDescription taskDescription,
             ComponentName topActivity) {
@@ -301,103 +272,6 @@
         this.topActivity = topActivity;
     }
 
-    @Deprecated
-    public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
-            String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
-            boolean isStackTask, boolean isSystemApp, boolean isDockable,
-            TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
-            boolean isLocked) {
-        this.key = key;
-        this.icon = icon;
-        this.thumbnail = thumbnail;
-        this.title = title;
-        this.titleDescription = titleDescription;
-        this.colorPrimary = colorPrimary;
-        this.colorBackground = colorBackground;
-        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
-                Color.WHITE) > 3f;
-        this.taskDescription = taskDescription;
-        this.isLaunchTarget = isLaunchTarget;
-        this.isStackTask = isStackTask;
-        this.isSystemApp = isSystemApp;
-        this.isDockable = isDockable;
-        this.resizeMode = resizeMode;
-        this.topActivity = topActivity;
-        this.isLocked = isLocked;
-    }
-
-    /**
-     * Copies the metadata from another task, but retains the current callbacks.
-     */
-    @Deprecated
-    public void copyFrom(Task o) {
-        this.key = o.key;
-        this.icon = o.icon;
-        this.thumbnail = o.thumbnail;
-        this.title = o.title;
-        this.titleDescription = o.titleDescription;
-        this.colorPrimary = o.colorPrimary;
-        this.colorBackground = o.colorBackground;
-        this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
-        this.taskDescription = o.taskDescription;
-        this.isLaunchTarget = o.isLaunchTarget;
-        this.isStackTask = o.isStackTask;
-        this.isSystemApp = o.isSystemApp;
-        this.isDockable = o.isDockable;
-        this.resizeMode = o.resizeMode;
-        this.isLocked = o.isLocked;
-        this.topActivity = o.topActivity;
-    }
-
-    /**
-     * Add a callback.
-     */
-    @Deprecated
-    public void addCallback(TaskCallbacks cb) {
-        if (!mCallbacks.contains(cb)) {
-            mCallbacks.add(cb);
-        }
-    }
-
-    /**
-     * Remove a callback.
-     */
-    @Deprecated
-    public void removeCallback(TaskCallbacks cb) {
-        mCallbacks.remove(cb);
-    }
-
-    /** Updates the task's windowing mode. */
-    @Deprecated
-    public void setWindowingMode(int windowingMode) {
-        key.setWindowingMode(windowingMode);
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskWindowingModeChanged();
-        }
-    }
-
-    /** Notifies the callback listeners that this task has been loaded */
-    @Deprecated
-    public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
-        this.icon = applicationIcon;
-        this.thumbnail = thumbnailData;
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
-        }
-    }
-
-    /** Notifies the callback listeners that this task has been unloaded */
-    @Deprecated
-    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
-        icon = defaultApplicationIcon;
-        thumbnail = null;
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            mCallbacks.get(i).onTaskDataUnloaded();
-        }
-    }
-
     /**
      * Returns the top activity component.
      */
@@ -424,9 +298,6 @@
         if (!isDockable) {
             writer.print(" dockable=N");
         }
-        if (isLaunchTarget) {
-            writer.print(" launchTarget=Y");
-        }
         if (isLocked) {
             writer.print(" locked=Y");
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java
new file mode 100644
index 0000000..15cf369
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PeopleProviderUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system;
+
+/**
+ *  These strings are part of the {@link com.android.systemui.people.PeopleProvider} API
+ *  contract. The API returns a People Tile preview that can be displayed by calling packages.
+ *  The provider is part of the SystemUI service, and the strings live here for shared access with
+ *  Launcher (caller).
+ */
+public class PeopleProviderUtils {
+    /**
+     * ContentProvider URI scheme.
+     * @hide
+     */
+    public static final String PEOPLE_PROVIDER_SCHEME = "content://";
+
+    /**
+     * ContentProvider URI authority.
+     * @hide
+     */
+    public static final String PEOPLE_PROVIDER_AUTHORITY =
+            "com.android.systemui.people.PeopleProvider";
+
+    /**
+     * Method name for getting People Tile preview.
+     * @hide
+     */
+    public static final String GET_PEOPLE_TILE_PREVIEW_METHOD = "get_people_tile_preview";
+
+    /**
+     * Extras bundle key specifying shortcut Id of the People Tile preview requested.
+     * @hide
+     */
+    public static final String EXTRAS_KEY_SHORTCUT_ID = "shortcut_id";
+
+    /**
+     * Extras bundle key specifying package name of the People Tile preview requested.
+     * @hide
+     */
+    public static final String EXTRAS_KEY_PACKAGE_NAME = "package_name";
+
+    /**
+     * Extras bundle key specifying {@code UserHandle} of the People Tile preview requested.
+     * @hide
+     */
+    public static final String EXTRAS_KEY_USER_HANDLE = "user_handle";
+
+    /**
+     * Response bundle key to access the returned People Tile preview.
+     * @hide
+     */
+    public static final String RESPONSE_KEY_REMOTE_VIEWS = "remote_views";
+
+    /**
+     * Name of the permission needed to get a People Tile preview for a given conversation shortcut.
+     * @hide
+     */
+    public static final String GET_PEOPLE_TILE_PREVIEW_PERMISSION =
+            "android.permission.GET_PEOPLE_TILE_PREVIEW";
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 59e81cf..0a117c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -16,37 +16,72 @@
 
 package com.android.keyguard;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.icu.text.NumberFormat;
 import android.util.MathUtils;
 
 import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.util.ViewController;
 
+import java.util.Locale;
+import java.util.Objects;
 import java.util.TimeZone;
 
 /**
- * Controls the color of a GradientTextClock.
+ * Controller for an AnimatableClockView.
  */
 public class AnimatableClockController extends ViewController<AnimatableClockView> {
+    private static final int FORMAT_NUMBER = 1234567890;
 
     private final StatusBarStateController mStatusBarStateController;
-    private final int[] mDozingColors = new int[] {Color.WHITE, Color.WHITE};
-    private int[] mLockScreenColors = new int[2];
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final int mDozingColor = Color.WHITE;
+    private int mLockScreenColor;
 
     private boolean mIsDozing;
+    private Locale mLocale;
+
+    private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
+    private final String mBurmeseNumerals;
+    private final float mBurmeseLineSpacing;
+    private final float mDefaultLineSpacing;
 
     public AnimatableClockController(
             AnimatableClockView view,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            BroadcastDispatcher broadcastDispatcher) {
         super(view);
         mStatusBarStateController = statusBarStateController;
         mIsDozing = mStatusBarStateController.isDozing();
+        mBroadcastDispatcher = broadcastDispatcher;
+
+        mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
+        mBurmeseLineSpacing = getContext().getResources().getFloat(
+                R.dimen.keyguard_clock_line_spacing_scale_burmese);
+        mDefaultLineSpacing = getContext().getResources().getFloat(
+                R.dimen.keyguard_clock_line_spacing_scale);
     }
 
+    private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateLocale();
+        }
+    };
+
     @Override
     protected void onViewAttached() {
+        updateLocale();
+        mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
         mStatusBarStateController.addCallback(mStatusBarStateListener);
         mIsDozing = mStatusBarStateController.isDozing();
         refreshTime();
@@ -55,6 +90,7 @@
 
     @Override
     protected void onViewDetached() {
+        mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
     }
 
@@ -84,11 +120,23 @@
         mView.refreshFormat();
     }
 
+    private void updateLocale() {
+        Locale currLocale = Locale.getDefault();
+        if (!Objects.equals(currLocale, mLocale)) {
+            mLocale = currLocale;
+            NumberFormat nf = NumberFormat.getInstance(mLocale);
+            if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
+                mView.setLineSpacingScale(mBurmeseLineSpacing);
+            } else {
+                mView.setLineSpacingScale(mDefaultLineSpacing);
+            }
+        }
+    }
+
     private void initColors() {
-        mLockScreenColors[0] = Utils.getColorAttrDefaultColor(getContext(),
+        mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
                 com.android.systemui.R.attr.wallpaperTextColor);
-        mLockScreenColors[1] = mLockScreenColors[0]; // same color
-        mView.setColors(mDozingColors, mLockScreenColors);
+        mView.setColors(mDozingColor, mLockScreenColor);
         mView.animateDoze(mIsDozing, false);
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index ca99563..64b3d73 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -44,12 +44,13 @@
 
     private final Calendar mTime = Calendar.getInstance();
 
-    private CharSequence mFormat;
-    private CharSequence mDescFormat;
-    private int[] mDozingColors;
-    private int[] mLockScreenColors;
     private final int mDozingWeight;
     private final int mLockScreenWeight;
+    private CharSequence mFormat;
+    private CharSequence mDescFormat;
+    private int mDozingColor;
+    private int mLockScreenColor;
+    private float mLineSpacingScale = 1f;
 
     private TextAnimator mTextAnimator = null;
     private Runnable mOnTextAnimatorInitialized;
@@ -111,8 +112,7 @@
                     () -> {
                         invalidate();
                         return Unit.INSTANCE;
-                    },
-                    2 /* number of lines (each can have a unique Paint) */);
+                    });
             if (mOnTextAnimatorInitialized != null) {
                 mOnTextAnimatorInitialized.run();
                 mOnTextAnimatorInitialized = null;
@@ -127,15 +127,20 @@
         mTextAnimator.draw(canvas);
     }
 
-    void setColors(int[] dozingColors, int[] lockScreenColors) {
-        mDozingColors = dozingColors;
-        mLockScreenColors = lockScreenColors;
+    void setLineSpacingScale(float scale) {
+        mLineSpacingScale = scale;
+        setLineSpacing(0, mLineSpacingScale);
+    }
+
+    void setColors(int dozingColor, int lockScreenColor) {
+        mDozingColor = dozingColor;
+        mLockScreenColor = lockScreenColor;
     }
 
     void animateDoze(boolean isDozing, boolean animate) {
         setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
                 -1,
-                isDozing ? mDozingColors : mLockScreenColors,
+                isDozing ? mDozingColor : mLockScreenColor,
                 animate);
     }
 
@@ -152,15 +157,15 @@
     private void setTextStyle(
             @IntRange(from = 0, to = 1000) int weight,
             @FloatRange(from = 0) float textSize,
-            int[] colors,
+            int color,
             boolean animate) {
         if (mTextAnimator != null) {
-            mTextAnimator.setTextStyle(weight, textSize, colors, animate, ANIM_DURATION, null);
+            mTextAnimator.setTextStyle(weight, textSize, color, animate, ANIM_DURATION, null);
         } else {
             // when the text animator is set, update its start values
             mOnTextAnimatorInitialized =
                     () -> mTextAnimator.setTextStyle(
-                            weight, textSize, colors, false, ANIM_DURATION, null);
+                            weight, textSize, color, false, ANIM_DURATION, null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index f6b03c1..e4f6e131 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,41 +24,14 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 import java.util.Locale;
 
 public class CarrierText extends TextView {
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "CarrierText";
+    private final boolean mShowMissingSim;
 
-    private static CharSequence mSeparator;
-
-    private boolean mShowMissingSim;
-
-    private boolean mShowAirplaneMode;
-    private boolean mShouldMarquee;
-
-    private CarrierTextController mCarrierTextController;
-
-    private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
-            new CarrierTextController.CarrierTextCallback() {
-                @Override
-                public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-                    setText(info.carrierText);
-                }
-
-                @Override
-                public void startedGoingToSleep() {
-                    setSelected(false);
-                }
-
-                @Override
-                public void finishedWakingUp() {
-                    setSelected(true);
-                }
-            };
+    private final boolean mShowAirplaneMode;
 
     public CarrierText(Context context) {
         this(context, null);
@@ -78,30 +51,6 @@
         }
         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
     }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mSeparator = getResources().getString(
-                com.android.internal.R.string.kg_text_message_separator);
-        mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
-                mShowMissingSim);
-        mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setSelected(mShouldMarquee); // Allow marquee to work.
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mCarrierTextController.setListening(mCarrierTextCallback);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mCarrierTextController.setListening(null);
-    }
-
     @Override
     protected void onVisibilityChanged(View changedView, int visibility) {
         super.onVisibilityChanged(changedView, visibility);
@@ -113,7 +62,15 @@
         }
     }
 
-    private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+    public boolean getShowAirplaneMode() {
+        return mShowAirplaneMode;
+    }
+
+    public boolean getShowMissingSim() {
+        return mShowMissingSim;
+    }
+
+    private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
         private final Locale mLocale;
         private final boolean mAllCaps;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index b1e1434..46a6d8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 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.
@@ -16,680 +16,61 @@
 
 package com.android.keyguard;
 
-import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-import static android.telephony.PhoneStateListener.LISTEN_NONE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.WirelessUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
+import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
 
 /**
- * Controller that generates text including the carrier names and/or the status of all the SIM
- * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
- * separated by a given separator {@link CharSequence}.
+ * Controller for {@link CarrierText}.
  */
-public class CarrierTextController {
-    private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "CarrierTextController";
+public class CarrierTextController extends ViewController<CarrierText> {
+    private final CarrierTextManager mCarrierTextManager;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private final boolean mIsEmergencyCallCapable;
-    private final Handler mMainHandler;
-    private final Handler mBgHandler;
-    private boolean mTelephonyCapable;
-    private boolean mShowMissingSim;
-    private boolean mShowAirplaneMode;
-    private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
-    @VisibleForTesting
-    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private WifiManager mWifiManager;
-    private boolean[] mSimErrorState;
-    private final int mSimSlotsNumber;
-    @Nullable // Check for nullability before dispatching
-    private CarrierTextCallback mCarrierTextCallback;
-    private Context mContext;
-    private CharSequence mSeparator;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
-    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
-            new WakefulnessLifecycle.Observer() {
+    private final CarrierTextManager.CarrierTextCallback mCarrierTextCallback =
+            new CarrierTextManager.CarrierTextCallback() {
                 @Override
-                public void onFinishedWakingUp() {
-                    final CarrierTextCallback callback = mCarrierTextCallback;
-                    if (callback != null) callback.finishedWakingUp();
+                public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
+                    mView.setText(info.carrierText);
                 }
 
                 @Override
-                public void onStartedGoingToSleep() {
-                    final CarrierTextCallback callback = mCarrierTextCallback;
-                    if (callback != null) callback.startedGoingToSleep();
+                public void startedGoingToSleep() {
+                    mView.setSelected(false);
+                }
+
+                @Override
+                public void finishedWakingUp() {
+                    mView.setSelected(true);
                 }
             };
 
-    @VisibleForTesting
-    protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onRefreshCarrierInfo() {
-            if (DEBUG) {
-                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
-                        + Boolean.toString(mTelephonyCapable));
-            }
-            updateCarrierText();
-        }
+    @Inject
+    public CarrierTextController(CarrierText view,
+            CarrierTextManager.Builder carrierTextManagerBuilder,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        super(view);
 
-        @Override
-        public void onTelephonyCapable(boolean capable) {
-            if (DEBUG) {
-                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
-                        + Boolean.toString(capable));
-            }
-            mTelephonyCapable = capable;
-            updateCarrierText();
-        }
-
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            if (slotId < 0 || slotId >= mSimSlotsNumber) {
-                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
-                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
-                return;
-            }
-
-            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
-            if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
-                mSimErrorState[slotId] = true;
-                updateCarrierText();
-            } else if (mSimErrorState[slotId]) {
-                mSimErrorState[slotId] = false;
-                updateCarrierText();
-            }
-        }
-    };
-
-    private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onActiveDataSubscriptionIdChanged(int subId) {
-            mActiveMobileDataSubscription = subId;
-            if (mNetworkSupported.get() && mCarrierTextCallback != null) {
-                updateCarrierText();
-            }
-        }
-    };
-
-    /**
-     * The status of this lock screen. Primarily used for widgets on LockScreen.
-     */
-    private enum StatusMode {
-        Normal, // Normal case (sim card present, it's not locked)
-        NetworkLocked, // SIM card is 'network locked'.
-        SimMissing, // SIM card is missing.
-        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
-        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
-        SimLocked, // SIM card is currently locked
-        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
-        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
-        SimIoError, // SIM card is faulty
-        SimUnknown // SIM card is unknown
+        mCarrierTextManager = carrierTextManagerBuilder
+                .setShowAirplaneMode(mView.getShowAirplaneMode())
+                .setShowMissingSim(mView.getShowMissingSim())
+                .build();
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
-    /**
-     * Controller that provides updates on text with carriers names or SIM status.
-     * Used by {@link CarrierText}.
-     *
-     * @param separator Separator between different parts of the text
-     */
-    public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
-            boolean showMissingSim) {
-        mContext = context;
-        mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
-
-        mShowAirplaneMode = showAirplaneMode;
-        mShowMissingSim = showMissingSim;
-
-        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        mSeparator = separator;
-        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
-        mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
-        mSimErrorState = new boolean[mSimSlotsNumber];
-        mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
-        mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mBgHandler.post(() -> {
-            boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
-                    ConnectivityManager.TYPE_MOBILE);
-            if (supported && mNetworkSupported.compareAndSet(false, supported)) {
-                // This will set/remove the listeners appropriately. Note that it will never double
-                // add the listeners.
-                handleSetListening(mCarrierTextCallback);
-            }
-        });
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
     }
 
-    private TelephonyManager getTelephonyManager() {
-        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    @Override
+    protected void onViewAttached() {
+        mCarrierTextManager.setListening(mCarrierTextCallback);
     }
 
-    /**
-     * Checks if there are faulty cards. Adds the text depending on the slot of the card
-     *
-     * @param text:   current carrier text based on the sim state
-     * @param carrierNames names order by subscription order
-     * @param subOrderBySlot array containing the sub index for each slot ID
-     * @param noSims: whether a valid sim card is inserted
-     * @return text
-     */
-    private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
-            CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
-        final CharSequence carrier = "";
-        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
-                TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
-        // mSimErrorState has the state of each sim indexed by slotID.
-        for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
-            if (!mSimErrorState[index]) {
-                continue;
-            }
-            // In the case when no sim cards are detected but a faulty card is inserted
-            // overwrite the text and only show "Invalid card"
-            if (noSims) {
-                return concatenate(carrierTextForSimIOError,
-                        getContext().getText(
-                                com.android.internal.R.string.emergency_calls_only),
-                        mSeparator);
-            } else if (subOrderBySlot[index] != -1) {
-                int subIndex = subOrderBySlot[index];
-                // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
-                carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
-                        carrierNames[subIndex],
-                        mSeparator);
-            } else {
-                // concatenate "Invalid card" when faulty card is inserted in other slot
-                text = concatenate(text, carrierTextForSimIOError, mSeparator);
-            }
-
-        }
-        return text;
-    }
-
-    /**
-     * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
-     * (assumed false to start). In that case, the following happens:
-     * <ul>
-     *     <li> If there was a registered callback, and the network is supported, it will register
-     *          listeners.
-     *     <li> If there was not a registered callback, it will try to remove unregistered listeners
-     *          which is a no-op
-     * </ul>
-     *
-     * This call will always be processed in a background thread.
-     */
-    private void handleSetListening(CarrierTextCallback callback) {
-        TelephonyManager telephonyManager = getTelephonyManager();
-        if (callback != null) {
-            mCarrierTextCallback = callback;
-            if (mNetworkSupported.get()) {
-                // Keyguard update monitor expects callbacks from main thread
-                mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
-                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
-                telephonyManager.listen(mPhoneStateListener,
-                        LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
-            } else {
-                // Don't listen and clear out the text when the device isn't a phone.
-                mMainHandler.post(() -> callback.updateCarrierInfo(
-                        new CarrierTextCallbackInfo("", null, false, null)
-                ));
-            }
-        } else {
-            mCarrierTextCallback = null;
-            mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
-            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
-            telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
-        }
-    }
-
-    /**
-     * Sets the listening status of this controller. If the callback is null, it is set to
-     * not listening.
-     *
-     * @param callback Callback to provide text updates
-     */
-    public void setListening(CarrierTextCallback callback) {
-        mBgHandler.post(() -> handleSetListening(callback));
-    }
-
-    protected List<SubscriptionInfo> getSubscriptionInfo() {
-        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
-    }
-
-    protected void updateCarrierText() {
-        boolean allSimsMissing = true;
-        boolean anySimReadyAndInService = false;
-        CharSequence displayText = null;
-        List<SubscriptionInfo> subs = getSubscriptionInfo();
-
-        final int numSubs = subs.size();
-        final int[] subsIds = new int[numSubs];
-        // This array will contain in position i, the index of subscription in slot ID i.
-        // -1 if no subscription in that slot
-        final int[] subOrderBySlot = new int[mSimSlotsNumber];
-        for (int i = 0; i < mSimSlotsNumber; i++) {
-            subOrderBySlot[i] = -1;
-        }
-        final CharSequence[] carrierNames = new CharSequence[numSubs];
-        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
-
-        for (int i = 0; i < numSubs; i++) {
-            int subId = subs.get(i).getSubscriptionId();
-            carrierNames[i] = "";
-            subsIds[i] = subId;
-            subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
-            int simState = mKeyguardUpdateMonitor.getSimState(subId);
-            CharSequence carrierName = subs.get(i).getCarrierName();
-            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
-            if (DEBUG) {
-                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
-            }
-            if (carrierTextForSimState != null) {
-                allSimsMissing = false;
-                carrierNames[i] = carrierTextForSimState;
-            }
-            if (simState == TelephonyManager.SIM_STATE_READY) {
-                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
-                if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
-                    // hack for WFC (IWLAN) not turning off immediately once
-                    // Wi-Fi is disassociated or disabled
-                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                            || (mWifiManager.isWifiEnabled()
-                            && mWifiManager.getConnectionInfo() != null
-                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
-                        }
-                        anySimReadyAndInService = true;
-                    }
-                }
-            }
-        }
-        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
-        // This condition will also be true always when numSubs == 0
-        if (allSimsMissing && !anySimReadyAndInService) {
-            if (numSubs != 0) {
-                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
-                // This depends on mPlmn containing the text "Emergency calls only" when the radio
-                // has some connectivity. Otherwise, it should be null or empty and just show
-                // "No SIM card"
-                // Grab the first subscripton, because they all should contain the emergency text,
-                // described above.
-                displayText = makeCarrierStringOnEmergencyCapable(
-                        getMissingSimMessage(), subs.get(0).getCarrierName());
-            } else {
-                // We don't have a SubscriptionInfo to get the emergency calls only from.
-                // Grab it from the old sticky broadcast if possible instead. We can use it
-                // here because no subscriptions are active, so we don't have
-                // to worry about MSIM clashing.
-                CharSequence text =
-                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
-                Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
-                if (i != null) {
-                    String spn = "";
-                    String plmn = "";
-                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
-                        spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
-                    }
-                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
-                        plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
-                    }
-                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
-                    if (Objects.equals(plmn, spn)) {
-                        text = plmn;
-                    } else {
-                        text = concatenate(plmn, spn, mSeparator);
-                    }
-                }
-                displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
-            }
-        }
-
-        if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
-
-        displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
-                allSimsMissing);
-
-        boolean airplaneMode = false;
-        // APM (airplane mode) != no carrier state. There are carrier services
-        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
-        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
-            displayText = getAirplaneModeMessage();
-            airplaneMode = true;
-        }
-
-        final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
-                displayText,
-                carrierNames,
-                !allSimsMissing,
-                subsIds,
-                airplaneMode);
-        postToCallback(info);
-    }
-
-    @VisibleForTesting
-    protected void postToCallback(CarrierTextCallbackInfo info) {
-        final CarrierTextCallback callback = mCarrierTextCallback;
-        if (callback != null) {
-            mMainHandler.post(() -> callback.updateCarrierInfo(info));
-        }
-    }
-
-    private Context getContext() {
-        return mContext;
-    }
-
-    private String getMissingSimMessage() {
-        return mShowMissingSim && mTelephonyCapable
-                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
-    }
-
-    private String getAirplaneModeMessage() {
-        return mShowAirplaneMode
-                ? getContext().getString(R.string.airplane_mode) : "";
-    }
-
-    /**
-     * Top-level function for creating carrier text. Makes text based on simState, PLMN
-     * and SPN as well as device capabilities, such as being emergency call capable.
-     *
-     * @return Carrier text if not in missing state, null otherwise.
-     */
-    private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
-        CharSequence carrierText = null;
-        CarrierTextController.StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case Normal:
-                carrierText = text;
-                break;
-
-            case SimNotReady:
-                // Null is reserved for denoting missing, in this case we have nothing to display.
-                carrierText = ""; // nothing to display yet.
-                break;
-
-            case NetworkLocked:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        mContext.getText(R.string.keyguard_network_locked_message), text);
-                break;
-
-            case SimMissing:
-                carrierText = null;
-                break;
-
-            case SimPermDisabled:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(
-                                R.string.keyguard_permanent_disabled_sim_message_short),
-                        text);
-                break;
-
-            case SimMissingLocked:
-                carrierText = null;
-                break;
-
-            case SimLocked:
-                carrierText = makeCarrierStringOnLocked(
-                        getContext().getText(R.string.keyguard_sim_locked_message),
-                        text);
-                break;
-
-            case SimPukLocked:
-                carrierText = makeCarrierStringOnLocked(
-                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
-                        text);
-                break;
-            case SimIoError:
-                carrierText = makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_sim_error_message_short),
-                        text);
-                break;
-            case SimUnknown:
-                carrierText = null;
-                break;
-        }
-
-        return carrierText;
-    }
-
-    /*
-     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
-     */
-    private CharSequence makeCarrierStringOnEmergencyCapable(
-            CharSequence simMessage, CharSequence emergencyCallMessage) {
-        if (mIsEmergencyCallCapable) {
-            return concatenate(simMessage, emergencyCallMessage, mSeparator);
-        }
-        return simMessage;
-    }
-
-    /*
-     * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
-     * DSDS
-     */
-    private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
-            CharSequence carrierName) {
-        final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
-        final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
-        if (simMessageValid && carrierNameValid) {
-            return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
-                    carrierName, simMessage);
-        } else if (simMessageValid) {
-            return simMessage;
-        } else if (carrierNameValid) {
-            return carrierName;
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Determine the current status of the lock screen given the SIM state and other stuff.
-     */
-    private CarrierTextController.StatusMode getStatusForIccState(int simState) {
-        final boolean missingAndNotProvisioned =
-                !mKeyguardUpdateMonitor.isDeviceProvisioned()
-                        && (simState == TelephonyManager.SIM_STATE_ABSENT
-                        || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
-
-        // Assume we're NETWORK_LOCKED if not provisioned
-        simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
-        switch (simState) {
-            case TelephonyManager.SIM_STATE_ABSENT:
-                return CarrierTextController.StatusMode.SimMissing;
-            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
-                return CarrierTextController.StatusMode.SimMissingLocked;
-            case TelephonyManager.SIM_STATE_NOT_READY:
-                return CarrierTextController.StatusMode.SimNotReady;
-            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
-                return CarrierTextController.StatusMode.SimLocked;
-            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
-                return CarrierTextController.StatusMode.SimPukLocked;
-            case TelephonyManager.SIM_STATE_READY:
-                return CarrierTextController.StatusMode.Normal;
-            case TelephonyManager.SIM_STATE_PERM_DISABLED:
-                return CarrierTextController.StatusMode.SimPermDisabled;
-            case TelephonyManager.SIM_STATE_UNKNOWN:
-                return CarrierTextController.StatusMode.SimUnknown;
-            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
-                return CarrierTextController.StatusMode.SimIoError;
-        }
-        return CarrierTextController.StatusMode.SimUnknown;
-    }
-
-    private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
-            CharSequence separator) {
-        final boolean plmnValid = !TextUtils.isEmpty(plmn);
-        final boolean spnValid = !TextUtils.isEmpty(spn);
-        if (plmnValid && spnValid) {
-            return new StringBuilder().append(plmn).append(separator).append(spn).toString();
-        } else if (plmnValid) {
-            return plmn;
-        } else if (spnValid) {
-            return spn;
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
-     * separator added so there are no extra separators that are not needed.
-     */
-    private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
-        int length = sequences.length;
-        if (length == 0) return "";
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < length; i++) {
-            if (!TextUtils.isEmpty(sequences[i])) {
-                if (!TextUtils.isEmpty(sb)) {
-                    sb.append(separator);
-                }
-                sb.append(sequences[i]);
-            }
-        }
-        return sb.toString();
-    }
-
-    private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
-        if (!TextUtils.isEmpty(string)) {
-            list.add(string);
-        }
-        return list;
-    }
-
-    private CharSequence getCarrierHelpTextForSimState(int simState,
-            String plmn, String spn) {
-        int carrierHelpTextId = 0;
-        CarrierTextController.StatusMode status = getStatusForIccState(simState);
-        switch (status) {
-            case NetworkLocked:
-                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
-                break;
-
-            case SimMissing:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
-                break;
-
-            case SimPermDisabled:
-                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
-                break;
-
-            case SimMissingLocked:
-                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
-                break;
-
-            case Normal:
-            case SimLocked:
-            case SimPukLocked:
-                break;
-        }
-
-        return mContext.getText(carrierHelpTextId);
-    }
-
-    public static class Builder {
-        private final Context mContext;
-        private final String mSeparator;
-        private boolean mShowAirplaneMode;
-        private boolean mShowMissingSim;
-
-        @Inject
-        public Builder(Context context, @Main Resources resources) {
-            mContext = context;
-            mSeparator = resources.getString(
-                    com.android.internal.R.string.kg_text_message_separator);
-        }
-
-
-        public Builder setShowAirplaneMode(boolean showAirplaneMode) {
-            mShowAirplaneMode = showAirplaneMode;
-            return this;
-        }
-
-        public Builder setShowMissingSim(boolean showMissingSim) {
-            mShowMissingSim = showMissingSim;
-            return this;
-        }
-
-        public CarrierTextController build() {
-            return new CarrierTextController(
-                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim);
-        }
-    }
-    /**
-     * Data structure for passing information to CarrierTextController subscribers
-     */
-    public static final class CarrierTextCallbackInfo {
-        public final CharSequence carrierText;
-        public final CharSequence[] listOfCarriers;
-        public final boolean anySimReady;
-        public final int[] subscriptionIds;
-        public boolean airplaneMode;
-
-        @VisibleForTesting
-        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
-                boolean anySimReady, int[] subscriptionIds) {
-            this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
-        }
-
-        @VisibleForTesting
-        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
-                boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
-            this.carrierText = carrierText;
-            this.listOfCarriers = listOfCarriers;
-            this.anySimReady = anySimReady;
-            this.subscriptionIds = subscriptionIds;
-            this.airplaneMode = airplaneMode;
-        }
-    }
-
-    /**
-     * Callback to communicate to Views
-     */
-    public interface CarrierTextCallback {
-        /**
-         * Provides updated carrier information.
-         */
-        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
-
-        /**
-         * Notifies the View that the device is going to sleep
-         */
-        default void startedGoingToSleep() {};
-
-        /**
-         * Notifies the View that the device finished waking up
-         */
-        default void finishedWakingUp() {};
+    @Override
+    protected void onViewDetached() {
+        mCarrierTextManager.setListening(null);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
new file mode 100644
index 0000000..87b01e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextManager {
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final String TAG = "CarrierTextController";
+
+    private final boolean mIsEmergencyCallCapable;
+    private final Handler mMainHandler;
+    private final Handler mBgHandler;
+    private boolean mTelephonyCapable;
+    private final boolean mShowMissingSim;
+    private final boolean mShowAirplaneMode;
+    private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
+    @VisibleForTesting
+    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final WifiManager mWifiManager;
+    private final boolean[] mSimErrorState;
+    private final int mSimSlotsNumber;
+    @Nullable // Check for nullability before dispatching
+    private CarrierTextCallback mCarrierTextCallback;
+    private final Context mContext;
+    private final TelephonyManager mTelephonyManager;
+    private final CharSequence mSeparator;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onFinishedWakingUp() {
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.finishedWakingUp();
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.startedGoingToSleep();
+                }
+            };
+
+    @VisibleForTesting
+    protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onRefreshCarrierInfo() {
+            if (DEBUG) {
+                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+                        + Boolean.toString(mTelephonyCapable));
+            }
+            updateCarrierText();
+        }
+
+        @Override
+        public void onTelephonyCapable(boolean capable) {
+            if (DEBUG) {
+                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+                        + Boolean.toString(capable));
+            }
+            mTelephonyCapable = capable;
+            updateCarrierText();
+        }
+
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            if (slotId < 0 || slotId >= mSimSlotsNumber) {
+                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+                        + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+                return;
+            }
+
+            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+            if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
+                mSimErrorState[slotId] = true;
+                updateCarrierText();
+            } else if (mSimErrorState[slotId]) {
+                mSimErrorState[slotId] = false;
+                updateCarrierText();
+            }
+        }
+    };
+
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+                updateCarrierText();
+            }
+        }
+    };
+
+    /**
+     * The status of this lock screen. Primarily used for widgets on LockScreen.
+     */
+    private enum StatusMode {
+        Normal, // Normal case (sim card present, it's not locked)
+        NetworkLocked, // SIM card is 'network locked'.
+        SimMissing, // SIM card is missing.
+        SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+        SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+        SimLocked, // SIM card is currently locked
+        SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimIoError, // SIM card is faulty
+        SimUnknown // SIM card is unknown
+    }
+
+    /**
+     * Controller that provides updates on text with carriers names or SIM status.
+     * Used by {@link CarrierText}.
+     *
+     * @param separator Separator between different parts of the text
+     */
+    private CarrierTextManager(Context context, CharSequence separator, boolean showAirplaneMode,
+            boolean showMissingSim, @Nullable WifiManager wifiManager,
+            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            WakefulnessLifecycle wakefulnessLifecycle, @Main Handler mainHandler,
+            @Background Handler bgHandler, KeyguardUpdateMonitor keyguardUpdateMonitor) {
+        mContext = context;
+        mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+
+        mShowAirplaneMode = showAirplaneMode;
+        mShowMissingSim = showMissingSim;
+
+        mWifiManager = wifiManager;
+        mTelephonyManager = telephonyManager;
+        mSeparator = separator;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
+        mSimErrorState = new boolean[mSimSlotsNumber];
+        mMainHandler = mainHandler;
+        mBgHandler = bgHandler;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mBgHandler.post(() -> {
+            boolean supported = connectivityManager.isNetworkSupported(
+                    ConnectivityManager.TYPE_MOBILE);
+            if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+                // This will set/remove the listeners appropriately. Note that it will never double
+                // add the listeners.
+                handleSetListening(mCarrierTextCallback);
+            }
+        });
+    }
+
+    private TelephonyManager getTelephonyManager() {
+        return mTelephonyManager;
+    }
+
+    /**
+     * Checks if there are faulty cards. Adds the text depending on the slot of the card
+     *
+     * @param text:   current carrier text based on the sim state
+     * @param carrierNames names order by subscription order
+     * @param subOrderBySlot array containing the sub index for each slot ID
+     * @param noSims: whether a valid sim card is inserted
+     * @return text
+     */
+    private CharSequence updateCarrierTextWithSimIoError(CharSequence text,
+            CharSequence[] carrierNames, int[] subOrderBySlot, boolean noSims) {
+        final CharSequence carrier = "";
+        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+                TelephonyManager.SIM_STATE_CARD_IO_ERROR, carrier);
+        // mSimErrorState has the state of each sim indexed by slotID.
+        for (int index = 0; index < getTelephonyManager().getActiveModemCount(); index++) {
+            if (!mSimErrorState[index]) {
+                continue;
+            }
+            // In the case when no sim cards are detected but a faulty card is inserted
+            // overwrite the text and only show "Invalid card"
+            if (noSims) {
+                return concatenate(carrierTextForSimIOError,
+                        getContext().getText(
+                                com.android.internal.R.string.emergency_calls_only),
+                        mSeparator);
+            } else if (subOrderBySlot[index] != -1) {
+                int subIndex = subOrderBySlot[index];
+                // prepend "Invalid card" when faulty card is inserted in slot 0 or 1
+                carrierNames[subIndex] = concatenate(carrierTextForSimIOError,
+                        carrierNames[subIndex],
+                        mSeparator);
+            } else {
+                // concatenate "Invalid card" when faulty card is inserted in other slot
+                text = concatenate(text, carrierTextForSimIOError, mSeparator);
+            }
+
+        }
+        return text;
+    }
+
+    /**
+     * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+     * (assumed false to start). In that case, the following happens:
+     * <ul>
+     *     <li> If there was a registered callback, and the network is supported, it will register
+     *          listeners.
+     *     <li> If there was not a registered callback, it will try to remove unregistered listeners
+     *          which is a no-op
+     * </ul>
+     *
+     * This call will always be processed in a background thread.
+     */
+    private void handleSetListening(CarrierTextCallback callback) {
+        TelephonyManager telephonyManager = getTelephonyManager();
+        if (callback != null) {
+            mCarrierTextCallback = callback;
+            if (mNetworkSupported.get()) {
+                // Keyguard update monitor expects callbacks from main thread
+                mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
+                mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+                telephonyManager.listen(mPhoneStateListener,
+                        LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+            } else {
+                // Don't listen and clear out the text when the device isn't a phone.
+                mMainHandler.post(() -> callback.updateCarrierInfo(
+                        new CarrierTextCallbackInfo("", null, false, null)
+                ));
+            }
+        } else {
+            mCarrierTextCallback = null;
+            mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+            telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
+        }
+    }
+
+    /**
+     * Sets the listening status of this controller. If the callback is null, it is set to
+     * not listening.
+     *
+     * @param callback Callback to provide text updates
+     */
+    public void setListening(CarrierTextCallback callback) {
+        mBgHandler.post(() -> handleSetListening(callback));
+    }
+
+    protected List<SubscriptionInfo> getSubscriptionInfo() {
+        return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+    }
+
+    protected void updateCarrierText() {
+        boolean allSimsMissing = true;
+        boolean anySimReadyAndInService = false;
+        CharSequence displayText = null;
+        List<SubscriptionInfo> subs = getSubscriptionInfo();
+
+        final int numSubs = subs.size();
+        final int[] subsIds = new int[numSubs];
+        // This array will contain in position i, the index of subscription in slot ID i.
+        // -1 if no subscription in that slot
+        final int[] subOrderBySlot = new int[mSimSlotsNumber];
+        for (int i = 0; i < mSimSlotsNumber; i++) {
+            subOrderBySlot[i] = -1;
+        }
+        final CharSequence[] carrierNames = new CharSequence[numSubs];
+        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+
+        for (int i = 0; i < numSubs; i++) {
+            int subId = subs.get(i).getSubscriptionId();
+            carrierNames[i] = "";
+            subsIds[i] = subId;
+            subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
+            int simState = mKeyguardUpdateMonitor.getSimState(subId);
+            CharSequence carrierName = subs.get(i).getCarrierName();
+            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+            if (DEBUG) {
+                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+            }
+            if (carrierTextForSimState != null) {
+                allSimsMissing = false;
+                carrierNames[i] = carrierTextForSimState;
+            }
+            if (simState == TelephonyManager.SIM_STATE_READY) {
+                ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+                if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+                    // hack for WFC (IWLAN) not turning off immediately once
+                    // Wi-Fi is disassociated or disabled
+                    if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                            || (mWifiManager != null && mWifiManager.isWifiEnabled()
+                            && mWifiManager.getConnectionInfo() != null
+                            && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+                        if (DEBUG) {
+                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+                        }
+                        anySimReadyAndInService = true;
+                    }
+                }
+            }
+        }
+        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+        // This condition will also be true always when numSubs == 0
+        if (allSimsMissing && !anySimReadyAndInService) {
+            if (numSubs != 0) {
+                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+                // This depends on mPlmn containing the text "Emergency calls only" when the radio
+                // has some connectivity. Otherwise, it should be null or empty and just show
+                // "No SIM card"
+                // Grab the first subscripton, because they all should contain the emergency text,
+                // described above.
+                displayText = makeCarrierStringOnEmergencyCapable(
+                        getMissingSimMessage(), subs.get(0).getCarrierName());
+            } else {
+                // We don't have a SubscriptionInfo to get the emergency calls only from.
+                // Grab it from the old sticky broadcast if possible instead. We can use it
+                // here because no subscriptions are active, so we don't have
+                // to worry about MSIM clashing.
+                CharSequence text =
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only);
+                Intent i = getContext().registerReceiver(null,
+                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
+                if (i != null) {
+                    String spn = "";
+                    String plmn = "";
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)) {
+                        spn = i.getStringExtra(TelephonyManager.EXTRA_SPN);
+                    }
+                    if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
+                        plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
+                    }
+                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+                    if (Objects.equals(plmn, spn)) {
+                        text = plmn;
+                    } else {
+                        text = concatenate(plmn, spn, mSeparator);
+                    }
+                }
+                displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+            }
+        }
+
+        if (TextUtils.isEmpty(displayText)) displayText = joinNotEmpty(mSeparator, carrierNames);
+
+        displayText = updateCarrierTextWithSimIoError(displayText, carrierNames, subOrderBySlot,
+                allSimsMissing);
+
+        boolean airplaneMode = false;
+        // APM (airplane mode) != no carrier state. There are carrier services
+        // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+        if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+            displayText = getAirplaneModeMessage();
+            airplaneMode = true;
+        }
+
+        final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
+                displayText,
+                carrierNames,
+                !allSimsMissing,
+                subsIds,
+                airplaneMode);
+        postToCallback(info);
+    }
+
+    @VisibleForTesting
+    protected void postToCallback(CarrierTextCallbackInfo info) {
+        final CarrierTextCallback callback = mCarrierTextCallback;
+        if (callback != null) {
+            mMainHandler.post(() -> callback.updateCarrierInfo(info));
+        }
+    }
+
+    private Context getContext() {
+        return mContext;
+    }
+
+    private String getMissingSimMessage() {
+        return mShowMissingSim && mTelephonyCapable
+                ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+    }
+
+    private String getAirplaneModeMessage() {
+        return mShowAirplaneMode
+                ? getContext().getString(R.string.airplane_mode) : "";
+    }
+
+    /**
+     * Top-level function for creating carrier text. Makes text based on simState, PLMN
+     * and SPN as well as device capabilities, such as being emergency call capable.
+     *
+     * @return Carrier text if not in missing state, null otherwise.
+     */
+    private CharSequence getCarrierTextForSimState(int simState, CharSequence text) {
+        CharSequence carrierText = null;
+        CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case Normal:
+                carrierText = text;
+                break;
+
+            case SimNotReady:
+                // Null is reserved for denoting missing, in this case we have nothing to display.
+                carrierText = ""; // nothing to display yet.
+                break;
+
+            case NetworkLocked:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        mContext.getText(R.string.keyguard_network_locked_message), text);
+                break;
+
+            case SimMissing:
+                carrierText = null;
+                break;
+
+            case SimPermDisabled:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(
+                                R.string.keyguard_permanent_disabled_sim_message_short),
+                        text);
+                break;
+
+            case SimMissingLocked:
+                carrierText = null;
+                break;
+
+            case SimLocked:
+                carrierText = makeCarrierStringOnLocked(
+                        getContext().getText(R.string.keyguard_sim_locked_message),
+                        text);
+                break;
+
+            case SimPukLocked:
+                carrierText = makeCarrierStringOnLocked(
+                        getContext().getText(R.string.keyguard_sim_puk_locked_message),
+                        text);
+                break;
+            case SimIoError:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_error_message_short),
+                        text);
+                break;
+            case SimUnknown:
+                carrierText = null;
+                break;
+        }
+
+        return carrierText;
+    }
+
+    /*
+     * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+     */
+    private CharSequence makeCarrierStringOnEmergencyCapable(
+            CharSequence simMessage, CharSequence emergencyCallMessage) {
+        if (mIsEmergencyCallCapable) {
+            return concatenate(simMessage, emergencyCallMessage, mSeparator);
+        }
+        return simMessage;
+    }
+
+    /*
+     * Add "SIM card is locked" in parenthesis after carrier name, so it is easily associated in
+     * DSDS
+     */
+    private CharSequence makeCarrierStringOnLocked(CharSequence simMessage,
+            CharSequence carrierName) {
+        final boolean simMessageValid = !TextUtils.isEmpty(simMessage);
+        final boolean carrierNameValid = !TextUtils.isEmpty(carrierName);
+        if (simMessageValid && carrierNameValid) {
+            return mContext.getString(R.string.keyguard_carrier_name_with_sim_locked_template,
+                    carrierName, simMessage);
+        } else if (simMessageValid) {
+            return simMessage;
+        } else if (carrierNameValid) {
+            return carrierName;
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Determine the current status of the lock screen given the SIM state and other stuff.
+     */
+    private CarrierTextManager.StatusMode getStatusForIccState(int simState) {
+        final boolean missingAndNotProvisioned =
+                !mKeyguardUpdateMonitor.isDeviceProvisioned()
+                        && (simState == TelephonyManager.SIM_STATE_ABSENT
+                        || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
+
+        // Assume we're NETWORK_LOCKED if not provisioned
+        simState = missingAndNotProvisioned ? TelephonyManager.SIM_STATE_NETWORK_LOCKED : simState;
+        switch (simState) {
+            case TelephonyManager.SIM_STATE_ABSENT:
+                return CarrierTextManager.StatusMode.SimMissing;
+            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
+                return CarrierTextManager.StatusMode.SimMissingLocked;
+            case TelephonyManager.SIM_STATE_NOT_READY:
+                return CarrierTextManager.StatusMode.SimNotReady;
+            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
+                return CarrierTextManager.StatusMode.SimLocked;
+            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
+                return CarrierTextManager.StatusMode.SimPukLocked;
+            case TelephonyManager.SIM_STATE_READY:
+                return CarrierTextManager.StatusMode.Normal;
+            case TelephonyManager.SIM_STATE_PERM_DISABLED:
+                return CarrierTextManager.StatusMode.SimPermDisabled;
+            case TelephonyManager.SIM_STATE_UNKNOWN:
+                return CarrierTextManager.StatusMode.SimUnknown;
+            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
+                return CarrierTextManager.StatusMode.SimIoError;
+        }
+        return CarrierTextManager.StatusMode.SimUnknown;
+    }
+
+    private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+            CharSequence separator) {
+        final boolean plmnValid = !TextUtils.isEmpty(plmn);
+        final boolean spnValid = !TextUtils.isEmpty(spn);
+        if (plmnValid && spnValid) {
+            return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+        } else if (plmnValid) {
+            return plmn;
+        } else if (spnValid) {
+            return spn;
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Joins the strings in a sequence using a separator. Empty strings are discarded with no extra
+     * separator added so there are no extra separators that are not needed.
+     */
+    private static CharSequence joinNotEmpty(CharSequence separator, CharSequence[] sequences) {
+        int length = sequences.length;
+        if (length == 0) return "";
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            if (!TextUtils.isEmpty(sequences[i])) {
+                if (!TextUtils.isEmpty(sb)) {
+                    sb.append(separator);
+                }
+                sb.append(sequences[i]);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+        if (!TextUtils.isEmpty(string)) {
+            list.add(string);
+        }
+        return list;
+    }
+
+    private CharSequence getCarrierHelpTextForSimState(int simState,
+            String plmn, String spn) {
+        int carrierHelpTextId = 0;
+        CarrierTextManager.StatusMode status = getStatusForIccState(simState);
+        switch (status) {
+            case NetworkLocked:
+                carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+                break;
+
+            case SimMissing:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+                break;
+
+            case SimPermDisabled:
+                carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+                break;
+
+            case SimMissingLocked:
+                carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+                break;
+
+            case Normal:
+            case SimLocked:
+            case SimPukLocked:
+                break;
+        }
+
+        return mContext.getText(carrierHelpTextId);
+    }
+
+    /** Injectable Buildeer for {@#link CarrierTextManager}. */
+    public static class Builder {
+        private final Context mContext;
+        private final String mSeparator;
+        private final WifiManager mWifiManager;
+        private final ConnectivityManager mConnectivityManager;
+        private final TelephonyManager mTelephonyManager;
+        private final WakefulnessLifecycle mWakefulnessLifecycle;
+        private final Handler mMainHandler;
+        private final Handler mBgHandler;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private boolean mShowAirplaneMode;
+        private boolean mShowMissingSim;
+
+        @Inject
+        public Builder(Context context, @Main Resources resources,
+                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+                TelephonyManager telephonyManager, WakefulnessLifecycle wakefulnessLifecycle,
+                @Main Handler mainHandler, @Background Handler bgHandler,
+                KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            mContext = context;
+            mSeparator = resources.getString(
+                    com.android.internal.R.string.kg_text_message_separator);
+            mWifiManager = wifiManager;
+            mConnectivityManager = connectivityManager;
+            mTelephonyManager = telephonyManager;
+            mWakefulnessLifecycle = wakefulnessLifecycle;
+            mMainHandler = mainHandler;
+            mBgHandler = bgHandler;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        }
+
+        /** */
+        public Builder setShowAirplaneMode(boolean showAirplaneMode) {
+            mShowAirplaneMode = showAirplaneMode;
+            return this;
+        }
+
+        /** */
+        public Builder setShowMissingSim(boolean showMissingSim) {
+            mShowMissingSim = showMissingSim;
+            return this;
+        }
+
+        /** Create a CarrierTextManager. */
+        public CarrierTextManager build() {
+            return new CarrierTextManager(
+                    mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+                    mConnectivityManager, mTelephonyManager, mWakefulnessLifecycle, mMainHandler,
+                    mBgHandler, mKeyguardUpdateMonitor);
+        }
+    }
+    /**
+     * Data structure for passing information to CarrierTextController subscribers
+     */
+    public static final class CarrierTextCallbackInfo {
+        public final CharSequence carrierText;
+        public final CharSequence[] listOfCarriers;
+        public final boolean anySimReady;
+        public final int[] subscriptionIds;
+        public boolean airplaneMode;
+
+        @VisibleForTesting
+        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds) {
+            this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+        }
+
+        @VisibleForTesting
+        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+            this.carrierText = carrierText;
+            this.listOfCarriers = listOfCarriers;
+            this.anySimReady = anySimReady;
+            this.subscriptionIds = subscriptionIds;
+            this.airplaneMode = airplaneMode;
+        }
+    }
+
+    /**
+     * Callback to communicate to Views
+     */
+    public interface CarrierTextCallback {
+        /**
+         * Provides updated carrier information.
+         */
+        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+        /**
+         * Notifies the View that the device is going to sleep
+         */
+        default void startedGoingToSleep() {};
+
+        /**
+         * Notifies the View that the device finished waking up
+         */
+        default void finishedWakingUp() {};
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 707ee29..c4b02f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -16,34 +16,16 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
 import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.widget.Button;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.util.EmergencyDialerConstants;
 
 /**
  * This class implements a smart emergency button that updates itself based
@@ -53,34 +35,14 @@
  */
 public class EmergencyButton extends Button {
 
-    private static final String LOG_TAG = "EmergencyButton";
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
 
     private int mDownX;
     private int mDownY;
-    KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-
-        @Override
-        public void onSimStateChanged(int subId, int slotId, int simState) {
-            updateEmergencyCallButton();
-        }
-
-        @Override
-        public void onPhoneStateChanged(int phoneState) {
-            updateEmergencyCallButton();
-        }
-    };
     private boolean mLongPressWasDragged;
 
-    public interface EmergencyButtonCallback {
-        public void onEmergencyButtonClickedWhenInCall();
-    }
-
     private LockPatternUtils mLockPatternUtils;
-    private PowerManager mPowerManager;
-    private EmergencyButtonCallback mEmergencyButtonCallback;
 
-    private final boolean mIsVoiceCapable;
     private final boolean mEnableEmergencyCallWhileSimLocked;
 
     public EmergencyButton(Context context) {
@@ -89,34 +51,15 @@
 
     public EmergencyButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
         mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
     }
 
-    private TelephonyManager getTelephonyManager() {
-        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mLockPatternUtils = new LockPatternUtils(mContext);
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        setOnClickListener(v -> takeEmergencyCallAction());
         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
             setOnLongClickListener(v -> {
                 if (!mLongPressWasDragged
@@ -127,7 +70,6 @@
                 return false;
             });
         }
-        whitelistIpcs(this::updateEmergencyCallButton);
     }
 
     @Override
@@ -165,65 +107,13 @@
         return super.performLongClick();
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateEmergencyCallButton();
-    }
-
-    /**
-     * Shows the emergency dialer or returns the user to the existing call.
-     */
-    public void takeEmergencyCallAction() {
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
-        if (mPowerManager != null) {
-            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
-        }
-        try {
-            ActivityTaskManager.getService().stopSystemLockTaskMode();
-        } catch (RemoteException e) {
-            Slog.w(LOG_TAG, "Failed to stop app pinning");
-        }
-        if (isInCall()) {
-            resumeCall();
-            if (mEmergencyButtonCallback != null) {
-                mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
-            }
-        } else {
-            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-            if (updateMonitor != null) {
-                updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
-            } else {
-                Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
-            }
-            TelecomManager telecomManager = getTelecommManager();
-            if (telecomManager == null) {
-                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
-                return;
-            }
-            Intent emergencyDialIntent =
-                    telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
-                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                            .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
-                                    EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
-
-            getContext().startActivityAsUser(emergencyDialIntent,
-                    ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
-                    new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
-        }
-    }
-
-    private void updateEmergencyCallButton() {
+    void updateEmergencyCallButton(boolean isInCall, boolean isVoiceCapable, boolean simLocked) {
         boolean visible = false;
-        if (mIsVoiceCapable) {
+        if (isVoiceCapable) {
             // Emergency calling requires voice capability.
-            if (isInCall()) {
+            if (isInCall) {
                 visible = true; // always show "return to call" if phone is off-hook
             } else {
-                final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
-                        .isSimPinVoiceSecure();
                 if (simLocked) {
                     // Some countries can't handle emergency calls while SIM is locked.
                     visible = mEnableEmergencyCallWhileSimLocked;
@@ -237,7 +127,7 @@
             setVisibility(View.VISIBLE);
 
             int textId;
-            if (isInCall()) {
+            if (isInCall) {
                 textId = com.android.internal.R.string.lockscreen_return_to_call;
             } else {
                 textId = com.android.internal.R.string.lockscreen_emergency_call;
@@ -247,26 +137,4 @@
             setVisibility(View.GONE);
         }
     }
-
-    public void setCallback(EmergencyButtonCallback callback) {
-        mEmergencyButtonCallback = callback;
-    }
-
-    /**
-     * Resumes a call in progress.
-     */
-    private void resumeCall() {
-        getTelecommManager().showInCallScreen(false);
-    }
-
-    /**
-     * @return {@code true} if there is a call currently in progress.
-     */
-    private boolean isInCall() {
-        return getTelecommManager().isInCall();
-    }
-
-    private TelecomManager getTelecommManager() {
-        return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
new file mode 100644
index 0000000..4275189
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -0,0 +1,196 @@
+/*
+ * 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.keyguard;
+
+import static com.android.systemui.DejankUtils.whitelistIpcs;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.keyguard.EmergencyButton}. */
+@KeyguardBouncerScope
+public class EmergencyButtonController extends ViewController<EmergencyButton> {
+    static final String LOG_TAG = "EmergencyButton";
+    private final ConfigurationController mConfigurationController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final TelephonyManager mTelephonyManager;
+    private final PowerManager mPowerManager;
+    private final ActivityTaskManager mActivityTaskManager;
+    private final TelecomManager mTelecomManager;
+    private final MetricsLogger mMetricsLogger;
+
+    private EmergencyButtonCallback mEmergencyButtonCallback;
+
+    private final KeyguardUpdateMonitorCallback mInfoCallback =
+            new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onSimStateChanged(int subId, int slotId, int simState) {
+            updateEmergencyCallButton();
+        }
+
+        @Override
+        public void onPhoneStateChanged(int phoneState) {
+            updateEmergencyCallButton();
+        }
+    };
+
+    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onConfigChanged(Configuration newConfig) {
+            updateEmergencyCallButton();
+        }
+    };
+
+    private EmergencyButtonController(@Nullable EmergencyButton view,
+            ConfigurationController configurationController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+            PowerManager powerManager, ActivityTaskManager activityTaskManager,
+            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+        super(view);
+        mConfigurationController = configurationController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mTelephonyManager = telephonyManager;
+        mPowerManager = powerManager;
+        mActivityTaskManager = activityTaskManager;
+        mTelecomManager = telecomManager;
+        mMetricsLogger = metricsLogger;
+    }
+
+    @Override
+    protected void onInit() {
+        whitelistIpcs(this::updateEmergencyCallButton);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mConfigurationController.addCallback(mConfigurationListener);
+        mView.setOnClickListener(v -> takeEmergencyCallAction());
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+        mConfigurationController.removeCallback(mConfigurationListener);
+    }
+
+    private void updateEmergencyCallButton() {
+        if (mView != null) {
+            mView.updateEmergencyCallButton(
+                    mTelecomManager != null && mTelecomManager.isInCall(),
+                    mTelephonyManager.isVoiceCapable(),
+                    mKeyguardUpdateMonitor.isSimPinVoiceSecure());
+        }
+    }
+
+    public void setEmergencyButtonCallback(EmergencyButtonCallback callback) {
+        mEmergencyButtonCallback = callback;
+    }
+    /**
+     * Shows the emergency dialer or returns the user to the existing call.
+     */
+    public void takeEmergencyCallAction() {
+        mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
+        if (mPowerManager != null) {
+            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        }
+        mActivityTaskManager.stopSystemLockTaskMode();
+        if (mTelecomManager != null && mTelecomManager.isInCall()) {
+            mTelecomManager.showInCallScreen(false);
+            if (mEmergencyButtonCallback != null) {
+                mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+            }
+        } else {
+            mKeyguardUpdateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+            if (mTelecomManager == null) {
+                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                return;
+            }
+            Intent emergencyDialIntent =
+                    mTelecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
+                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                            .putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+                                    EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON);
+
+            getContext().startActivityAsUser(emergencyDialIntent,
+                    ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+                    new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+        }
+    }
+
+    /** */
+    public interface EmergencyButtonCallback {
+        /** */
+        void onEmergencyButtonClickedWhenInCall();
+    }
+
+    /** Injectable Factory for creating {@link EmergencyButtonController}. */
+    public static class Factory {
+        private final ConfigurationController mConfigurationController;
+        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final TelephonyManager mTelephonyManager;
+        private final PowerManager mPowerManager;
+        private final ActivityTaskManager mActivityTaskManager;
+        @Nullable
+        private final TelecomManager mTelecomManager;
+        private final MetricsLogger mMetricsLogger;
+
+        @Inject
+        public Factory(ConfigurationController configurationController,
+                KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
+                PowerManager powerManager, ActivityTaskManager activityTaskManager,
+                @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
+
+            mConfigurationController = configurationController;
+            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mTelephonyManager = telephonyManager;
+            mPowerManager = powerManager;
+            mActivityTaskManager = activityTaskManager;
+            mTelecomManager = telecomManager;
+            mMetricsLogger = metricsLogger;
+        }
+
+        /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
+        public EmergencyButtonController create(EmergencyButton view) {
+            return new EmergencyButtonController(view, mConfigurationController,
+                    mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
+                    mTelecomManager, mMetricsLogger);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 5760565..7a05a17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -31,7 +31,7 @@
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
@@ -41,6 +41,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
+    private final EmergencyButtonController mEmergencyButtonController;
     private CountDownTimer mCountdownTimer;
     protected KeyguardMessageAreaController mMessageAreaController;
     private boolean mDismissing;
@@ -70,11 +71,12 @@
             LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker) {
-        super(view, securityMode, keyguardSecurityCallback);
+            LatencyTracker latencyTracker, EmergencyButtonController emergencyButtonController) {
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
+        mEmergencyButtonController = emergencyButtonController;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = messageAreaControllerFactory.create(kma);
     }
@@ -83,6 +85,7 @@
 
     @Override
     public void onInit() {
+        super.onInit();
         mMessageAreaController.init();
     }
 
@@ -91,10 +94,7 @@
         super.onViewAttached();
         mView.setKeyDownListener(mKeyDownListener);
         mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(mEmergencyButtonCallback);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index e0de180..e375877 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -28,6 +28,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ClockPlugin;
@@ -56,6 +57,7 @@
     private final ClockManager mClockManager;
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final NotificationIconAreaController mNotificationIconAreaController;
+    private final BroadcastDispatcher mBroadcastDispatcher;
 
     /**
      * Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
@@ -101,7 +103,8 @@
             SysuiColorExtractor colorExtractor, ClockManager clockManager,
             KeyguardSliceViewController keyguardSliceViewController,
             NotificationIconAreaController notificationIconAreaController,
-            ContentResolver contentResolver) {
+            ContentResolver contentResolver,
+            BroadcastDispatcher broadcastDispatcher) {
         super(keyguardClockSwitch);
         mResources = resources;
         mStatusBarStateController = statusBarStateController;
@@ -109,6 +112,7 @@
         mClockManager = clockManager;
         mKeyguardSliceViewController = keyguardSliceViewController;
         mNotificationIconAreaController = notificationIconAreaController;
+        mBroadcastDispatcher = broadcastDispatcher;
         mTimeFormat = Settings.System.getString(contentResolver, Settings.System.TIME_12_24);
     }
 
@@ -231,12 +235,14 @@
                 mNewLockScreenClockViewController =
                         new AnimatableClockController(
                                 mView.findViewById(R.id.animatable_clock_view),
-                                mStatusBarStateController);
+                                mStatusBarStateController,
+                                mBroadcastDispatcher);
                 mNewLockScreenClockViewController.init();
                 mNewLockScreenLargeClockViewController =
                         new AnimatableClockController(
                                 mView.findViewById(R.id.animatable_clock_view_large),
-                                mStatusBarStateController);
+                                mStatusBarStateController,
+                                mBroadcastDispatcher);
                 mNewLockScreenLargeClockViewController.init();
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 276036c..76a7473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -46,12 +45,15 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
 
     private MediaRouter mMediaRouter = null;
     private final DisplayManager mDisplayService;
+    private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
 
@@ -85,9 +87,11 @@
 
     @Inject
     public KeyguardDisplayManager(Context context,
+            Lazy<NavigationBarController> navigationBarControllerLazy,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
             @UiBackground Executor uiBgExecutor) {
         mContext = context;
+        mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
         mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -240,7 +244,7 @@
         // Leave this task to {@link StatusBarKeyguardViewManager}
         if (displayId == DEFAULT_DISPLAY) return;
 
-        NavigationBarView navBarView = Dependency.get(NavigationBarController.class)
+        NavigationBarView navBarView = mNavigationBarControllerLazy.get()
                 .getNavigationBarView(displayId);
         // We may not have nav bar on a display.
         if (navBarView == null) return;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 1c691e7..a0c5958 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -41,6 +42,7 @@
     private final SecurityMode mSecurityMode;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final EmergencyButton mEmergencyButton;
+    private final EmergencyButtonController mEmergencyButtonController;
     private boolean mPaused;
 
 
@@ -68,11 +70,18 @@
     };
 
     protected KeyguardInputViewController(T view, SecurityMode securityMode,
-            KeyguardSecurityCallback keyguardSecurityCallback) {
+            KeyguardSecurityCallback keyguardSecurityCallback,
+            EmergencyButtonController emergencyButtonController) {
         super(view);
         mSecurityMode = securityMode;
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
+        mEmergencyButtonController = emergencyButtonController;
+    }
+
+    @Override
+    protected void onInit() {
+        mEmergencyButtonController.init();
     }
 
     @Override
@@ -154,9 +163,11 @@
         private final InputMethodManager mInputMethodManager;
         private final DelayableExecutor mMainExecutor;
         private final Resources mResources;
-        private LiftToActivateListener mLiftToActivateListener;
-        private TelephonyManager mTelephonyManager;
+        private final LiftToActivateListener mLiftToActivateListener;
+        private final TelephonyManager mTelephonyManager;
+        private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
         private final FalsingCollector mFalsingCollector;
+        private final boolean mIsNewLayoutEnabled;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -165,7 +176,10 @@
                 KeyguardMessageAreaController.Factory messageAreaControllerFactory,
                 InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
                 @Main Resources resources, LiftToActivateListener liftToActivateListener,
-                TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+                TelephonyManager telephonyManager,
+                EmergencyButtonController.Factory emergencyButtonControllerFactory,
+                FalsingCollector falsingCollector,
+                FeatureFlags featureFlags) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -175,36 +189,49 @@
             mResources = resources;
             mLiftToActivateListener = liftToActivateListener;
             mTelephonyManager = telephonyManager;
+            mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
             mFalsingCollector = falsingCollector;
+            mIsNewLayoutEnabled = featureFlags.isKeyguardLayoutEnabled();
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
         public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
                 SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
+            EmergencyButtonController emergencyButtonController =
+                    mEmergencyButtonControllerFactory.create(
+                            keyguardInputView.findViewById(R.id.emergency_call_button));
+
             if (keyguardInputView instanceof KeyguardPatternView) {
                 return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
-                        keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory);
+                        keyguardSecurityCallback, mLatencyTracker,
+                        emergencyButtonController,
+                        mMessageAreaControllerFactory);
             } else if (keyguardInputView instanceof KeyguardPasswordView) {
                 return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mInputMethodManager, mMainExecutor, mResources);
+                        mInputMethodManager, emergencyButtonController, mMainExecutor, mResources);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mFalsingCollector);
+                        mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+                        mIsNewLayoutEnabled);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+                        mLiftToActivateListener, mTelephonyManager,
+                        emergencyButtonController,
+                        mFalsingCollector, mIsNewLayoutEnabled);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
+                        mLiftToActivateListener, mTelephonyManager,
+                        emergencyButtonController,
+                        mFalsingCollector, mIsNewLayoutEnabled);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0f1c3c8..2e45545 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -111,10 +111,11 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             InputMethodManager inputMethodManager,
+            EmergencyButtonController emergencyButtonController,
             @Main DelayableExecutor mainExecutor,
             @Main Resources resources) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker);
+                messageAreaControllerFactory, latencyTracker, emergencyButtonController);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mMainExecutor = mainExecutor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 2aaf748..55e348c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -31,7 +31,7 @@
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockPatternView.Cell;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
+import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -50,6 +50,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
     private final LatencyTracker mLatencyTracker;
+    private final EmergencyButtonController mEmergencyButtonController;
     private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
 
     private KeyguardMessageAreaController mMessageAreaController;
@@ -179,11 +180,13 @@
             LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             LatencyTracker latencyTracker,
+            EmergencyButtonController emergencyButtonController,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
-        super(view, securityMode, keyguardSecurityCallback);
+        super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mLatencyTracker = latencyTracker;
+        mEmergencyButtonController = emergencyButtonController;
         mMessageAreaControllerFactory = messageAreaControllerFactory;
         KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
         mMessageAreaController = mMessageAreaControllerFactory.create(kma);
@@ -205,11 +208,7 @@
                 KeyguardUpdateMonitor.getCurrentUser()));
         // vibrate mode will be the same for the life of this screen
         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
-
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(mEmergencyButtonCallback);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
 
         View cancelBtn = mView.findViewById(R.id.cancel_button);
         if (cancelBtn != null) {
@@ -224,10 +223,7 @@
     protected void onViewDetached() {
         super.onViewDetached();
         mLockPatternView.setOnPatternListener(null);
-        EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
-        if (button != null) {
-            button.setCallback(null);
-        }
+        mEmergencyButtonController.setEmergencyButtonCallback(null);
         View cancelBtn = mView.findViewById(R.id.cancel_button);
         if (cancelBtn != null) {
             cancelBtn.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 6a6b964..825ea25 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,6 +167,20 @@
     }
 
     /**
+     * By default, the new layout will be enabled. When false, revert to the old style.
+     */
+    public void setIsNewLayoutEnabled(boolean isEnabled) {
+        if (!isEnabled) {
+            for (int i = 0; i < mButtons.length; i++) {
+                mButtons[i].disableNewLayout();
+            }
+            mDeleteButton.disableNewLayout();
+            mOkButton.disableNewLayout();
+            reloadColors();
+        }
+    }
+
+    /**
      * Reload colors from resources.
      **/
     public void reloadColors() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f247948..1b5aa45 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -71,9 +71,10 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
             LiftToActivateListener liftToActivateListener,
+            EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker);
+                messageAreaControllerFactory, latencyTracker, emergencyButtonController);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index c0aa2af..a456d42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -34,11 +34,13 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            FalsingCollector falsingCollector) {
+            EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,7 +23,6 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 
@@ -49,24 +48,27 @@
     private final boolean mIsPukScreenAvailable;
 
     private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     @Inject
-    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
+    KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mIsPukScreenAvailable = resources.getBoolean(
                 com.android.internal.R.bool.config_enable_puk_unlock_screen);
         mLockPatternUtils = lockPatternUtils;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
     public SecurityMode getSecurityMode(int userId) {
-        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
-
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
         if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
+                mKeyguardUpdateMonitor.getNextSubIdForState(
+                        TelephonyManager.SIM_STATE_PIN_REQUIRED))) {
             return SecurityMode.SimPin;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index f1b504e..33d47fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -44,15 +44,18 @@
     private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
             new ArrayList<>();
     private final LayoutInflater mLayoutInflater;
+    private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
     private final Factory mKeyguardSecurityViewControllerFactory;
 
     @Inject
     protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
             LayoutInflater layoutInflater,
-            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
+            KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
+            EmergencyButtonController.Factory emergencyButtonControllerFactory) {
         super(view);
         mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
         mLayoutInflater = layoutInflater;
+        mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
     }
 
     @Override
@@ -111,7 +114,8 @@
 
         if (childController == null) {
             childController = new NullKeyguardInputViewController(
-                    securityMode, keyguardSecurityCallback);
+                    securityMode, keyguardSecurityCallback,
+                    mEmergencyButtonControllerFactory.create(null));
         }
 
         return childController;
@@ -140,8 +144,9 @@
     private static class NullKeyguardInputViewController
             extends KeyguardInputViewController<KeyguardInputView> {
         protected NullKeyguardInputViewController(SecurityMode securityMode,
-                KeyguardSecurityCallback keyguardSecurityCallback) {
-            super(null, securityMode, keyguardSecurityCallback);
+                KeyguardSecurityCallback keyguardSecurityCallback,
+                EmergencyButtonController emergencyButtonController) {
+            super(null, securityMode, keyguardSecurityCallback, emergencyButtonController);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index b218141..4d2ebbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -78,13 +78,15 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 890a17c..0d9bb6f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,7 +37,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
 
@@ -85,13 +84,15 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
+            TelephonyManager telephonyManager, EmergencyButtonController emergencyButtonController,
+            FalsingCollector falsingCollector, boolean isNewLayoutEnabled) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                falsingCollector);
+                emergencyButtonController, falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
+        view.setIsNewLayoutEnabled(isNewLayoutEnabled);
     }
 
     @Override
@@ -196,8 +197,7 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
         } else {
-            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
-                    .getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
             if (info != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index fb97a30..1fbf71d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -49,10 +49,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
 
 import java.io.FileDescriptor;
@@ -317,6 +315,22 @@
                 R.dimen.widget_label_font_size);
         mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.header_row_font_size);
+
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onDensityOrFontScaleChanged();
+            }
+        }
+    }
+
+    void onOverlayChanged() {
+        for (int i = 0; i < mRow.getChildCount(); i++) {
+            View child = mRow.getChildAt(i);
+            if (child instanceof KeyguardSliceTextView) {
+                ((KeyguardSliceTextView) child).onOverlayChanged();
+            }
+        }
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -479,8 +493,7 @@
      * Representation of an item that appears under the clock on main keyguard message.
      */
     @VisibleForTesting
-    static class KeyguardSliceTextView extends TextView implements
-            ConfigurationController.ConfigurationListener {
+    static class KeyguardSliceTextView extends TextView {
         private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
         @StyleRes
@@ -492,24 +505,10 @@
             setEllipsize(TruncateAt.END);
         }
 
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-            Dependency.get(ConfigurationController.class).addCallback(this);
-        }
-
-        @Override
-        protected void onDetachedFromWindow() {
-            super.onDetachedFromWindow();
-            Dependency.get(ConfigurationController.class).removeCallback(this);
-        }
-
-        @Override
         public void onDensityOrFontScaleChanged() {
             updatePadding();
         }
 
-        @Override
         public void onOverlayChanged() {
             setTextAppearance(sStyleId);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 1b0a7fa..8038ce4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -83,6 +83,10 @@
         public void onDensityOrFontScaleChanged() {
             mView.onDensityOrFontScaleChanged();
         }
+        @Override
+        public void onOverlayChanged() {
+            mView.onOverlayChanged();
+        }
     };
 
     Observer<Slice> mObserver = new Observer<Slice>() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index fea152a..5db4f9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -34,7 +34,6 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 import java.io.FileDescriptor;
@@ -56,7 +55,6 @@
     private final IActivityManager mIActivityManager;
 
     private TextView mLogoutView;
-    private boolean mCanShowLogout = true; // by default, try to show the logout button here
     private KeyguardClockSwitch mClockView;
     private TextView mOwnerInfo;
     private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here
@@ -130,11 +128,6 @@
         }
     }
 
-    void setCanShowLogout(boolean canShowLogout) {
-        mCanShowLogout = canShowLogout;
-        updateLogoutView();
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -159,10 +152,7 @@
         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
         onSliceContentChanged();
 
-        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
-        setEnableMarquee(shouldMarquee);
         updateOwnerInfo();
-        updateLogoutView();
         updateDark();
     }
 
@@ -209,11 +199,11 @@
         return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0;
     }
 
-    void updateLogoutView() {
+    void updateLogoutView(boolean shouldShowLogout) {
         if (mLogoutView == null) {
             return;
         }
-        mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE);
+        mLogoutView.setVisibility(shouldShowLogout ? VISIBLE : GONE);
         // Logout button will stay in language of user 0 if we don't set that manually.
         mLogoutView.setText(mContext.getResources().getString(
                 com.android.internal.R.string.global_action_logout));
@@ -313,11 +303,6 @@
         }
     }
 
-    private boolean shouldShowLogout() {
-        return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
-                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
-    }
-
     private void onLogoutClicked(View view) {
         int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
         try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a5f364d..bfe7f8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,13 +16,10 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.View;
 
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -50,13 +47,12 @@
 
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final KeyguardClockSwitchController mKeyguardClockSwitchController;
-    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final ConfigurationController mConfigurationController;
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final DozeParameters mDozeParameters;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
 
-    private boolean mKeyguardStatusViewVisibilityAnimating;
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
     @Inject
@@ -72,16 +68,19 @@
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
-        mKeyguardStateController = keyguardStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
         mNotificationIconAreaController = notificationIconAreaController;
         mDozeParameters = dozeParameters;
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
+                dozeParameters);
     }
 
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+        mView.setEnableMarquee(mKeyguardUpdateMonitor.isDeviceInteractive());
+        mView.updateLogoutView(shouldShowLogout());
     }
 
     @Override
@@ -144,7 +143,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             mView.setAlpha(alpha);
         }
     }
@@ -200,7 +199,7 @@
     public void updatePosition(int x, int y, float scale, boolean animate) {
         // We animate the status view visible/invisible using Y translation, so don't change it
         // while the animation is running.
-        if (!mKeyguardStatusViewVisibilityAnimating) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
             PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
                     animate);
         }
@@ -230,69 +229,8 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        mView.animate().cancel();
-        mKeyguardStatusViewVisibilityAnimating = false;
-        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
-                && statusBarState != KEYGUARD) || goingToFullShade) {
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.animate()
-                    .alpha(0f)
-                    .setStartDelay(0)
-                    .setDuration(160)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(
-                    mAnimateKeyguardStatusViewGoneEndRunnable);
-            if (keyguardFadingAway) {
-                mView.animate()
-                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
-                        .start();
-            }
-        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
-            mView.setVisibility(View.VISIBLE);
-            mKeyguardStatusViewVisibilityAnimating = true;
-            mView.setAlpha(0f);
-            mView.animate()
-                    .alpha(1f)
-                    .setStartDelay(0)
-                    .setDuration(320)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == KEYGUARD) {
-            if (keyguardFadingAway) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-                mView.animate()
-                        .alpha(0)
-                        .translationYBy(-getHeight() * 0.05f)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .setDuration(125)
-                        .setStartDelay(0)
-                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
-                        .start();
-            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
-                mKeyguardStatusViewVisibilityAnimating = true;
-
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(0f);
-
-                float curTranslationY = mView.getTranslationY();
-                mView.setTranslationY(curTranslationY - getHeight() * 0.1f);
-                mView.animate()
-                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .alpha(1f)
-                        .translationY(curTranslationY)
-                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
-                        .start();
-            } else {
-                mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(1f);
-            }
-        } else {
-            mView.setVisibility(View.GONE);
-            mView.setAlpha(1f);
-        }
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
     }
 
     private void refreshTime() {
@@ -310,6 +248,11 @@
         }
     }
 
+    private boolean shouldShowLogout() {
+        return mKeyguardUpdateMonitor.isLogoutEnabled()
+                && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
+    }
+
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
@@ -336,12 +279,12 @@
                 mKeyguardSliceViewController.updateTopMargin(
                         mKeyguardClockSwitchController.getClockTextTopPadding());
                 mView.setCanShowOwnerInfo(false);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             } else {
                 // reset margin
                 mKeyguardSliceViewController.updateTopMargin(0);
                 mView.setCanShowOwnerInfo(true);
-                mView.setCanShowLogout(false);
+                mView.updateLogoutView(false);
             }
             updateAodIcons();
         }
@@ -367,7 +310,7 @@
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
                 refreshTime();
                 mView.updateOwnerInfo();
-                mView.updateLogoutView();
+                mView.updateLogoutView(shouldShowLogout());
             }
         }
 
@@ -385,27 +328,12 @@
         public void onUserSwitchComplete(int userId) {
             mKeyguardClockSwitchController.refreshFormat();
             mView.updateOwnerInfo();
-            mView.updateLogoutView();
+            mView.updateLogoutView(shouldShowLogout());
         }
 
         @Override
         public void onLogoutEnabledChanged() {
-            mView.updateLogoutView();
+            mView.updateLogoutView(shouldShowLogout());
         }
     };
-
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.INVISIBLE);
-    };
-
-
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-        mView.setVisibility(View.GONE);
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
-        mKeyguardStatusViewVisibilityAnimating = false;
-    };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
new file mode 100644
index 0000000..724e1f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+/**
+ * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
+ * This logic is shared by both the keyguard status view and the keyguard user switcher.
+ */
+public class KeyguardVisibilityHelper {
+
+    private View mView;
+    private final KeyguardStateController mKeyguardStateController;
+    private final DozeParameters mDozeParameters;
+    private boolean mKeyguardViewVisibilityAnimating;
+
+    public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController,
+            DozeParameters dozeParameters) {
+        mView = view;
+        mKeyguardStateController = keyguardStateController;
+        mDozeParameters = dozeParameters;
+    }
+
+    public boolean isVisibilityAnimating() {
+        return mKeyguardViewVisibilityAnimating;
+    }
+
+    /**
+     * Set the visibility of a keyguard view based on some new state.
+     */
+    public void setViewVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mView.animate().cancel();
+        mKeyguardViewVisibilityAnimating = false;
+        if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+                && statusBarState != KEYGUARD) || goingToFullShade) {
+            mKeyguardViewVisibilityAnimating = true;
+            mView.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setDuration(160)
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .withEndAction(
+                            mAnimateKeyguardStatusViewGoneEndRunnable);
+            if (keyguardFadingAway) {
+                mView.animate()
+                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+                        .start();
+            }
+        } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+            mView.setVisibility(View.VISIBLE);
+            mKeyguardViewVisibilityAnimating = true;
+            mView.setAlpha(0f);
+            mView.animate()
+                    .alpha(1f)
+                    .setStartDelay(0)
+                    .setDuration(320)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+        } else if (statusBarState == KEYGUARD) {
+            if (keyguardFadingAway) {
+                mKeyguardViewVisibilityAnimating = true;
+                mView.animate()
+                        .alpha(0)
+                        .translationYBy(-mView.getHeight() * 0.05f)
+                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+                        .setDuration(125)
+                        .setStartDelay(0)
+                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+                        .start();
+            } else if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+                mKeyguardViewVisibilityAnimating = true;
+
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(0f);
+
+                float curTranslationY = mView.getTranslationY();
+                mView.setTranslationY(curTranslationY - mView.getHeight() * 0.1f);
+                mView.animate()
+                        .setStartDelay((int) (StackStateAnimator.ANIMATION_DURATION_WAKEUP * .6f))
+                        .setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP)
+                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                        .alpha(1f)
+                        .translationY(curTranslationY)
+                        .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
+                        .start();
+            } else {
+                mView.setVisibility(View.VISIBLE);
+                mView.setAlpha(1f);
+            }
+        } else {
+            mView.setVisibility(View.GONE);
+            mView.setAlpha(1f);
+        }
+    }
+
+    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.INVISIBLE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.GONE);
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+        mKeyguardViewVisibilityAnimating = false;
+    };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3728106..886c372 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -16,17 +16,23 @@
 package com.android.keyguard;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
 
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
 /**
  * Similar to the {@link NumPadKey}, but displays an image.
  */
 public class NumPadButton extends AlphaOptimizedImageButton {
 
-    private final NumPadAnimator mAnimator;
+    private NumPadAnimator mAnimator;
 
     public NumPadButton(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -36,25 +42,34 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams());
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
+        super.setLayoutParams(params);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        // Set width/height to the same value to ensure a smooth circle for the bg
-        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+        // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+        // the height to match the old pin bouncer
+        int width = getMeasuredWidth();
+        int height = mAnimator == null ? (int) (width * .75f) : width;
+
+        setMeasuredDimension(getMeasuredWidth(), height);
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        mAnimator.onLayout(b - t);
+        if (mAnimator != null) mAnimator.onLayout(b - t);
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        mAnimator.start();
+        if (mAnimator != null) mAnimator.start();
         return super.onTouchEvent(event);
     }
 
@@ -62,6 +77,23 @@
      * Reload colors from resources.
      **/
     public void reloadColors() {
-        mAnimator.reloadColors(getContext());
+        if (mAnimator != null) {
+            mAnimator.reloadColors(getContext());
+        } else {
+            // Needed for old style pin
+            int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)
+                    .getDefaultColor();
+            ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
+        }
+    }
+
+    /**
+     * By default, the new layout will be enabled. Invoking will revert to the old style
+     */
+    public void disableNewLayout() {
+        mAnimator = null;
+        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+        setBackground(getContext().getResources().getDrawable(
+                R.drawable.ripple_drawable_pin, ctw.getTheme()));
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 756d610..01e1c63 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -22,6 +22,7 @@
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -47,7 +48,7 @@
     private int mTextViewResId;
     private PasswordTextView mTextView;
 
-    private final NumPadAnimator mAnimator;
+    private NumPadAnimator mAnimator;
 
     private View.OnClickListener mListener = new View.OnClickListener() {
         @Override
@@ -131,6 +132,17 @@
     }
 
     /**
+     * By default, the new layout will be enabled. Invoking will revert to the old style
+     */
+    public void disableNewLayout() {
+        findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
+        mAnimator = null;
+        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+        setBackground(getContext().getResources().getDrawable(
+                R.drawable.ripple_drawable_pin, ctw.getTheme()));
+    }
+
+    /**
      * Reload colors from resources.
      **/
     public void reloadColors() {
@@ -141,7 +153,7 @@
         mDigitText.setTextColor(textColor);
         mKlondikeText.setTextColor(klondikeColor);
 
-        mAnimator.reloadColors(getContext());
+        if (mAnimator != null) mAnimator.reloadColors(getContext());
     }
 
     @Override
@@ -150,14 +162,14 @@
             doHapticKeyClick();
         }
 
-        mAnimator.start();
+        if (mAnimator != null) mAnimator.start();
 
         return super.onTouchEvent(event);
     }
 
     @Override
     public void setLayoutParams(ViewGroup.LayoutParams params) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
+        if (mAnimator != null) mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
         super.setLayoutParams(params);
     }
@@ -167,8 +179,12 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         measureChildren(widthMeasureSpec, heightMeasureSpec);
 
-        // Set width/height to the same value to ensure a smooth circle for the bg
-        setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
+        // Set width/height to the same value to ensure a smooth circle for the bg, but shrink
+        // the height to match the old pin bouncer
+        int width = getMeasuredWidth();
+        int height = mAnimator == null ? (int) (width * .75f) : width;
+
+        setMeasuredDimension(getMeasuredWidth(), height);
     }
 
     @Override
@@ -187,7 +203,7 @@
         left = centerX - mKlondikeText.getMeasuredWidth() / 2;
         mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
 
-        mAnimator.onLayout(b - t);
+        if (mAnimator != null) mAnimator.onLayout(b - t);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index f2d36d1..5735a4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -54,11 +54,10 @@
  */
 class TextAnimator(
     layout: Layout,
-    private val invalidateCallback: () -> Unit,
-    private val numLines: Int = 1
+    private val invalidateCallback: () -> Unit
 ) {
     // Following two members are for mutable for testing purposes.
-    internal var textInterpolator: TextInterpolator = TextInterpolator(layout, numLines)
+    internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
     internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
         duration = DEFAULT_ANIMATION_DURATION
         addUpdateListener {
@@ -99,7 +98,7 @@
     fun setTextStyle(
         weight: Int = -1,
         textSize: Float = -1f,
-        colors: IntArray? = null,
+        color: Int? = null,
         animate: Boolean = true,
         duration: Long = -1L,
         interpolator: TimeInterpolator? = null
@@ -110,21 +109,13 @@
         }
 
         if (textSize >= 0) {
-            for (targetPaint in textInterpolator.targetPaint)
-                targetPaint.textSize = textSize
+            textInterpolator.targetPaint.textSize = textSize
         }
         if (weight >= 0) {
-            for (targetPaint in textInterpolator.targetPaint)
-                targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+            textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
         }
-        if (colors != null) {
-            require(colors.size == textInterpolator.targetPaint.size) {
-                "colors size (${colors.size}) must be the same size as" +
-                    " targetPaints size (${textInterpolator.targetPaint.size})," +
-                    " which was initialized as numLines ($numLines)"
-            }
-            for ((index, targetPaint) in textInterpolator.targetPaint.withIndex())
-                targetPaint.color = colors[index]
+        if (color != null) {
+            textInterpolator.targetPaint.color = color
         }
         textInterpolator.onTargetPaintModified()
 
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 0d41a2f..5d5797c 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -30,8 +30,7 @@
  * Provide text style linear interpolation for plain text.
  */
 class TextInterpolator(
-    layout: Layout,
-    lines: Int = 1
+    layout: Layout
 ) {
 
     /**
@@ -40,11 +39,9 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate base text
      * layout.
      *
-     * @return an array list of paint objects representing one paint per line of text. If this
-     * list has a smaller size than the number of lines, all extra lines will use the Paint an
-     * index 0.
+     * @return a paint object
      */
-    val basePaint = createDefaultPaint(layout.paint, lines)
+    val basePaint = TextPaint(layout.paint)
 
     /**
      * Returns target paint used for interpolation.
@@ -52,18 +49,9 @@
      * Once you modified the style parameters, you have to call reshapeText to recalculate target
      * text layout.
      *
-     * @return an array list of paint objects representing one paint per line of text. If this
-     * list has a smaller size than the number of lines, all extra lines will use the Paint an
-     * index 0.
+     * @return a paint object
      */
-    val targetPaint = createDefaultPaint(layout.paint, lines)
-
-    private fun createDefaultPaint(paint: TextPaint, lines: Int): ArrayList<TextPaint> {
-        val paintList = ArrayList<TextPaint>()
-        for (i in 0 until lines)
-            paintList.add(TextPaint(paint))
-        return paintList
-    }
+    val targetPaint = TextPaint(layout.paint)
 
     /**
      * A class represents a single font run.
@@ -102,7 +90,7 @@
     private val fontInterpolator = FontInterpolator()
 
     // Recycling object for glyph drawing. Will be extended for the longest font run if needed.
-    private val tmpDrawPaints = ArrayList<TextPaint>()
+    private val tmpDrawPaint = TextPaint()
     private var tmpPositionArray = FloatArray(20)
 
     /**
@@ -216,10 +204,10 @@
         if (progress == 0f) {
             return
         } else if (progress == 1f) {
-            updatePaint(basePaint, targetPaint)
+            basePaint.set(targetPaint)
         } else {
-            lerp(basePaint, targetPaint, progress, tmpDrawPaints)
-            updatePaint(basePaint, tmpDrawPaints)
+            lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+            basePaint.set(tmpDrawPaint)
         }
 
         lines.forEach { line ->
@@ -237,21 +225,13 @@
         progress = 0f
     }
 
-    companion object {
-        fun updatePaint(toUpdate: ArrayList<TextPaint>, newValues: ArrayList<TextPaint>) {
-            toUpdate.clear()
-            for (paint in newValues)
-                toUpdate.add(TextPaint(paint))
-        }
-    }
-
     /**
      * Draws interpolated text at the given progress.
      *
      * @param canvas a canvas.
      */
     fun draw(canvas: Canvas) {
-        lerp(basePaint, targetPaint, progress, tmpDrawPaints)
+        lerp(basePaint, targetPaint, progress, tmpDrawPaint)
         lines.forEachIndexed { lineNo, line ->
             line.runs.forEach { run ->
                 canvas.save()
@@ -261,10 +241,7 @@
                     canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
 
                     run.fontRuns.forEach { fontRun ->
-                        if (lineNo >= tmpDrawPaints.size)
-                            drawFontRun(canvas, run, fontRun, tmpDrawPaints[0])
-                        else
-                            drawFontRun(canvas, run, fontRun, tmpDrawPaints[lineNo])
+                        drawFontRun(canvas, run, fontRun, tmpDrawPaint)
                     }
                 } finally {
                     canvas.restore()
@@ -430,27 +407,19 @@
     }
 
     // Linear interpolate the paint.
-    private fun lerp(
-        from: ArrayList<TextPaint>,
-        to: ArrayList<TextPaint>,
-        progress: Float,
-        out: ArrayList<TextPaint>
-    ) {
-        out.clear()
+    private fun lerp(from: Paint, to: Paint, progress: Float, out: Paint) {
+        out.set(from)
+
         // Currently only font size & colors are interpolated.
         // TODO(172943390): Add other interpolation or support custom interpolator.
-        for (index in from.indices) {
-            val paint = TextPaint(from[index])
-            paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress)
-            paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress)
-            out.add(paint)
-        }
+        out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
+        out.color = ColorUtils.blendARGB(from.color, to.color, progress)
     }
 
     // Shape the text and stores the result to out argument.
     private fun shapeText(
         layout: Layout,
-        paints: ArrayList<TextPaint>
+        paint: TextPaint
     ): List<List<PositionedGlyphs>> {
         val out = mutableListOf<List<PositionedGlyphs>>()
         for (lineNo in 0 until layout.lineCount) { // Shape all lines.
@@ -458,7 +427,7 @@
             val count = layout.getLineEnd(lineNo) - lineStart
             val runs = mutableListOf<PositionedGlyphs>()
             TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
-                    paints[lineNo]) { _, _, glyphs, _ ->
+                    paint) { _, _, glyphs, _ ->
                 runs.add(glyphs)
             }
             out.add(runs)
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
similarity index 63%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
index 14d57bf..c4be1ba535 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java
@@ -14,6 +14,20 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.clock;
 
-parcelable ExternalTimeSuggestion;
+import java.util.List;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger Module for clock package. */
+@Module
+public abstract class ClockModule {
+
+    /** */
+    @Provides
+    public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
+        return clockManager.getClockInfos();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
index 5ef35be..b6413cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
@@ -28,11 +28,12 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
 
 import java.io.FileNotFoundException;
 import java.util.List;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Exposes custom clock face options and provides realistic preview images.
@@ -65,15 +66,12 @@
     private static final String CONTENT_SCHEME = "content";
     private static final String AUTHORITY = "com.android.keyguard.clock";
 
-    private final Supplier<List<ClockInfo>> mClocksSupplier;
-
-    public ClockOptionsProvider() {
-        this(() -> Dependency.get(ClockManager.class).getClockInfos());
-    }
+    @Inject
+    public Provider<List<ClockInfo>> mClockInfosProvider;
 
     @VisibleForTesting
-    ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) {
-        mClocksSupplier = clocksSupplier;
+    ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
+        mClockInfosProvider = clockInfosProvider;
     }
 
     @Override
@@ -99,7 +97,7 @@
         }
         MatrixCursor cursor = new MatrixCursor(new String[] {
                 COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
-        List<ClockInfo> clocks = mClocksSupplier.get();
+        List<ClockInfo> clocks = mClockInfosProvider.get();
         for (int i = 0; i < clocks.size(); i++) {
             ClockInfo clock = clocks.get(i);
             cursor.newRow()
@@ -139,7 +137,7 @@
             throw new FileNotFoundException("Invalid preview url, missing id");
         }
         ClockInfo clock = null;
-        List<ClockInfo> clocks = mClocksSupplier.get();
+        List<ClockInfo> clocks = mClockInfosProvider.get();
         for (int i = 0; i < clocks.size(); i++) {
             if (id.equals(clocks.get(i).getId())) {
                 clock = clocks.get(i);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
new file mode 100644
index 0000000..3a0357d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.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.keyguard.dagger;
+
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardQsUserSwitch and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardQsUserSwitchComponent {
+    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardQsUserSwitchComponent build(
+                @BindsInstance UserAvatarView userAvatarView);
+    }
+
+    /** Builds a {@link KeyguardQsUserSwitchController}. */
+    KeyguardQsUserSwitchController getKeyguardQsUserSwitchController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
new file mode 100644
index 0000000..49a617e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.android.keyguard.dagger;
+
+import com.android.keyguard.KeyguardStatusViewController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusViewComponent}
+ */
+@Subcomponent(modules = {KeyguardStatusBarViewModule.class})
+@KeyguardStatusBarViewScope
+public interface KeyguardStatusBarViewComponent {
+    /** Simple factory for {@link KeyguardStatusBarViewComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view);
+    }
+
+    /** Builds a {@link KeyguardStatusViewController}. */
+    KeyguardStatusBarViewController getKeyguardStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
new file mode 100644
index 0000000..a672523
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -0,0 +1,34 @@
+/*
+ * 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.android.keyguard.dagger;
+
+import com.android.keyguard.CarrierText;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link KeyguardStatusBarViewComponent}. */
+@Module
+public abstract class KeyguardStatusBarViewModule {
+    @Provides
+    @KeyguardStatusBarViewScope
+    static CarrierText getCarrierText(KeyguardStatusBarView view) {
+        return view.findViewById(R.id.keyguard_carrier_text);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
new file mode 100644
index 0000000..ba0642f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.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 com.android.keyguard.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the StatusBarComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardStatusBarViewScope {}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
index 1b6476c..d342377 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -25,6 +25,8 @@
 
 /**
  * Subcomponent for helping work with KeyguardStatusView and its children.
+ *
+ * TODO: unify this with {@link KeyguardStatusBarViewComponent}
  */
 @Subcomponent(modules = {KeyguardStatusViewModule.class})
 @KeyguardStatusViewScope
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.java
new file mode 100644
index 0000000..730c14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherComponent.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.keyguard.dagger;
+
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Subcomponent for helping work with KeyguardUserSwitcher and its children.
+ */
+@Subcomponent(modules = {KeyguardUserSwitcherModule.class})
+@KeyguardUserSwitcherScope
+public interface KeyguardUserSwitcherComponent {
+    /** Simple factory for {@link KeyguardUserSwitcherComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        KeyguardUserSwitcherComponent build(
+                @BindsInstance KeyguardUserSwitcherView keyguardUserSwitcherView);
+    }
+
+    /** Builds a {@link com.android.systemui.statusbar.policy.KeyguardUserSwitcherController}. */
+    KeyguardUserSwitcherController getKeyguardUserSwitcherController();
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
similarity index 76%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
index 14d57bf..b9184f4 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherModule.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import dagger.Module;
+
+/** Dagger module for {@link KeyguardUserSwitcherComponent}. */
+@Module
+public abstract class KeyguardUserSwitcherModule {
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
index 14d57bf..864472e 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardUserSwitcherScope.java
@@ -14,6 +14,19 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.keyguard.dagger;
 
-parcelable ExternalTimeSuggestion;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the KeyguardUserSwitcherComponent.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface KeyguardUserSwitcherScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index caaee5f..1765627 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -49,7 +49,6 @@
 
 import androidx.annotation.StyleRes;
 
-import com.android.settingslib.Utils;
 import com.android.settingslib.graph.ThemedBatteryDrawable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher;
@@ -105,11 +104,6 @@
     private DualToneHandler mDualToneHandler;
     private int mUser;
 
-    /**
-     * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
-     */
-    private boolean mUseWallpaperTextColors;
-
     private int mNonAdaptedSingleToneColor;
     private int mNonAdaptedForegroundColor;
     private int mNonAdaptedBackgroundColor;
@@ -242,31 +236,6 @@
         mIsSubscribedForTunerUpdates = false;
     }
 
-    /**
-     * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
-     * revert back to dark-mode-based/tinted colors.
-     *
-     * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
-     *                                    components
-     */
-    public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
-        if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
-            return;
-        }
-
-        mUseWallpaperTextColors = shouldUseWallpaperTextColor;
-
-        if (mUseWallpaperTextColors) {
-            updateColors(
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
-                    Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
-        } else {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
-    }
-
     public void setColorsFromContext(Context context) {
         if (context == null) {
             return;
@@ -476,13 +445,19 @@
         mNonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
         mNonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity);
 
-        if (!mUseWallpaperTextColors) {
-            updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
-                    mNonAdaptedSingleToneColor);
-        }
+        updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
+                mNonAdaptedSingleToneColor);
     }
 
-    private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
+    /**
+     * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events,
+     * if registered.
+     *
+     * @param foregroundColor
+     * @param backgroundColor
+     * @param singleToneColor
+     */
+    public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
         mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
         mTextColor = singleToneColor;
         if (mBatteryPercentView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 71ec33e..b6a232d 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui;
 
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
@@ -26,13 +29,16 @@
 import android.util.Size;
 import android.view.SurfaceHolder;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.glwallpaper.EglHelper;
-import com.android.systemui.glwallpaper.GLWallpaperRenderer;
 import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 
@@ -45,8 +51,13 @@
     // We delayed destroy render context that subsequent render requests have chance to cancel it.
     // This is to avoid destroying then recreating render context in a very short time.
     private static final int DELAY_FINISH_RENDERING = 1000;
+    private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
     private static final boolean DEBUG = false;
+    private ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
     private HandlerThread mWorker;
+    // scaled down version
+    private Bitmap mMiniBitmap;
 
     @Inject
     public ImageWallpaper() {
@@ -70,6 +81,7 @@
         super.onDestroy();
         mWorker.quitSafely();
         mWorker = null;
+        mMiniBitmap = null;
     }
 
     class GLEngine extends Engine {
@@ -80,7 +92,7 @@
         @VisibleForTesting
         static final int MIN_SURFACE_HEIGHT = 64;
 
-        private GLWallpaperRenderer mRenderer;
+        private ImageWallpaperRenderer mRenderer;
         private EglHelper mEglHelper;
         private final Runnable mFinishRenderingTask = this::finishRendering;
         private boolean mNeedRedraw;
@@ -101,6 +113,12 @@
             setFixedSizeAllowed(true);
             setOffsetNotificationsEnabled(false);
             updateSurfaceSize();
+            mMiniBitmap = null;
+            if (mWorker == null || mWorker.getThreadHandler() == null) {
+                updateMiniBitmap();
+            } else {
+                mWorker.getThreadHandler().post(this::updateMiniBitmap);
+            }
         }
 
         EglHelper getEglHelperInstance() {
@@ -111,6 +129,20 @@
             return new ImageWallpaperRenderer(getDisplayContext());
         }
 
+        private void updateMiniBitmap() {
+            mRenderer.useBitmap(b -> {
+                int size = Math.min(b.getWidth(), b.getHeight());
+                float scale = 1.0f;
+                if (size > MIN_SURFACE_WIDTH) {
+                    scale = (float) MIN_SURFACE_WIDTH / (float) size;
+                }
+                mMiniBitmap = Bitmap.createScaledBitmap(b, Math.round(scale * b.getWidth()),
+                        Math.round(scale * b.getHeight()), false);
+                computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
+                mLocalColorsToAdd.clear();
+            });
+        }
+
         private void updateSurfaceSize() {
             SurfaceHolder holder = getSurfaceHolder();
             Size frameSize = mRenderer.reportSurfaceSize();
@@ -126,6 +158,7 @@
 
         @Override
         public void onDestroy() {
+            mMiniBitmap = null;
             mWorker.getThreadHandler().post(() -> {
                 mRenderer.finish();
                 mRenderer = null;
@@ -134,6 +167,61 @@
             });
         }
 
+
+
+        @Override
+        public boolean supportsLocalColorExtraction() {
+            return true;
+        }
+
+        @Override
+        public void addLocalColorsAreas(@NonNull List<RectF> regions) {
+            mWorker.getThreadHandler().post(() -> {
+                Bitmap bitmap = mMiniBitmap;
+                if (bitmap == null) {
+                    mLocalColorsToAdd.addAll(regions);
+                } else {
+                    computeAndNotifyLocalColors(regions, bitmap);
+                }
+            });
+        }
+
+        private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
+            List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
+            try {
+                notifyLocalColorsChanged(regions, colors);
+            } catch (RuntimeException e) {
+                Log.e(TAG, e.getMessage(), e);
+            }
+        }
+
+        @Override
+        public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
+            // No-OP
+        }
+
+        private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
+                Bitmap b) {
+            List<WallpaperColors> colors = new ArrayList<>(areas.size());
+            for (int i = 0; i < areas.size(); i++) {
+                RectF area = areas.get(i);
+                if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
+                    colors.add(null);
+                    continue;
+                }
+                Rect subImage = new Rect(
+                        Math.round(area.left * b.getWidth()),
+                        Math.round(area.top * b.getHeight()),
+                        Math.round(area.right * b.getWidth()),
+                        Math.round(area.bottom * b.getHeight()));
+                Bitmap colorImg = Bitmap.createBitmap(b,
+                        subImage.left, subImage.top, subImage.width(), subImage.height());
+                WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
+                colors.add(color);
+            }
+            return colors;
+        }
+
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
             if (mWorker == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 38a82f8..36937d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,10 +16,19 @@
 
 package com.android.systemui.controls.dagger
 
+import android.content.ContentResolver
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
@@ -28,15 +37,43 @@
  * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
  *
  * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
- * instantiated if `featureEnabled` is true.
+ * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
  */
 @SysUISingleton
 class ControlsComponent @Inject constructor(
     @ControlsFeatureEnabled private val featureEnabled: Boolean,
+    private val context: Context,
     private val lazyControlsController: Lazy<ControlsController>,
     private val lazyControlsUiController: Lazy<ControlsUiController>,
-    private val lazyControlsListingController: Lazy<ControlsListingController>
+    private val lazyControlsListingController: Lazy<ControlsListingController>,
+    private val lockPatternUtils: LockPatternUtils,
+    private val keyguardStateController: KeyguardStateController,
+    private val userTracker: UserTracker,
+    private val secureSettings: SecureSettings
 ) {
+
+    private val contentResolver: ContentResolver
+        get() = context.contentResolver
+
+    private var canShowWhileLockedSetting = false
+
+    val showWhileLockedObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            updateShowWhileLocked()
+        }
+    }
+
+    init {
+        if (featureEnabled) {
+            secureSettings.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
+                false, /* notifyForDescendants */
+                showWhileLockedObserver
+            )
+            updateShowWhileLocked()
+        }
+    }
+
     fun getControlsController(): Optional<ControlsController> {
         return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
     }
@@ -52,4 +89,37 @@
             Optional.empty()
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * @return true if controls are feature-enabled and have available services to serve controls
+     */
+    fun isEnabled() = featureEnabled && lazyControlsController.get().available
+
+    /**
+     * Returns one of 3 states:
+     * * AVAILABLE - Controls can be made visible
+     * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock
+     * * UNAVAILABLE - Controls are not enabled
+     */
+    fun getVisibility(): Visibility {
+        if (!isEnabled()) return Visibility.UNAVAILABLE
+        if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+                == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
+            return Visibility.AVAILABLE_AFTER_UNLOCK
+        }
+        if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+            return Visibility.AVAILABLE_AFTER_UNLOCK
+        }
+
+        return Visibility.AVAILABLE
+    }
+
+    private fun updateShowWhileLocked() {
+        canShowWhileLockedSetting = secureSettings.getInt(
+            Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0
+    }
+
+    enum class Visibility {
+        AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 247f25e..6b300f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -30,6 +30,7 @@
 import android.service.controls.actions.FloatAction
 import android.util.Log
 import android.view.HapticFeedbackConstants
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@
     }
 
     override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             cvh.action(BooleanAction(templateId, !isChecked))
         }, true /* blockable */))
@@ -79,7 +80,7 @@
 
     override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
         val blockable = cvh.usePanel()
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             if (cvh.usePanel()) {
                 showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@
     }
 
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
         }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
-        bouncerOrRun(Action(cvh.cws.ci.controlId, {
+        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             // Long press snould only be called when there is valid control state, otherwise ignore
             cvh.cws.control?.let {
                 cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@
     }
 
     override fun runPendingAction(controlId: String) {
+        if (!keyguardStateController.isUnlocked()) return
         if (pendingAction?.controlId == controlId) {
             pendingAction?.invoke()
             pendingAction = null
@@ -135,7 +137,8 @@
             false
         }
 
-    private fun bouncerOrRun(action: Action) {
+    @VisibleForTesting
+    fun bouncerOrRun(action: Action) {
         if (keyguardStateController.isShowing()) {
             var closeDialog = !keyguardStateController.isUnlocked()
             if (closeDialog) {
@@ -190,6 +193,10 @@
         }
     }
 
+    @VisibleForTesting
+    fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
+        Action(controlId, f, blockable)
+
     inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
         fun invoke() {
             if (!blockable || shouldRunAction(controlId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
index f533cfb..db68d17 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -28,11 +28,12 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import javax.inject.Inject
 
 /**
  * Show the controls space inside a dialog, as from the lock screen.
  */
-class ControlsDialog(
+class ControlsDialog @Inject constructor(
     thisContext: Context,
     val broadcastDispatcher: BroadcastDispatcher
 ) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index ec4a91c..2b362b9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.screenrecord.ScreenRecordDialog;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -92,4 +93,10 @@
     @IntoMap
     @ClassKey(TvNotificationPanelActivity.class)
     public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
+
+    /** Inject into PeopleSpaceActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(PeopleSpaceActivity.class)
+    public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index ffb8446..91c2dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
@@ -146,4 +147,9 @@
      * Member injection into the supplied argument.
      */
     void inject(KeyguardSliceProvider keyguardSliceProvider);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(ClockOptionsProvider clockOptionsProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b0067cd..b67db03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -22,6 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.clock.ClockModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
@@ -90,6 +91,7 @@
 @Module(includes = {
             AppOpsModule.class,
             AssistModule.class,
+            ClockModule.class,
             ControlsModule.class,
             DemoModeModule.class,
             FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 84dd259..f3726a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.dagger;
 
+import android.content.Context;
+
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.tv.TvWMComponent;
+import com.android.systemui.wmshell.TvWMShellModule;
 import com.android.systemui.wmshell.WMShellModule;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.ShellInit;
@@ -34,7 +39,13 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for WindowManager.
+ * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
+ * from the WM component into the SysUI component (in
+ * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * provided by its particular device/form-factor SystemUI implementation.
+ *
+ * ie. {@link WMComponent} includes {@link WMShellModule}
+ *     and {@link TvWMComponent} includes {@link TvWMShellModule}
  */
 @WMSingleton
 @Subcomponent(modules = {WMShellModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 8af45a5..d85b101 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -25,6 +25,8 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 
 import android.animation.Animator;
@@ -250,6 +252,7 @@
     private final IWindowManager mIWindowManager;
     private final Executor mBackgroundExecutor;
     private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
+    private ControlsComponent mControlsComponent;
     private Optional<ControlsController> mControlsControllerOptional;
     private final RingerModeTracker mRingerModeTracker;
     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
@@ -338,6 +341,7 @@
         mSysuiColorExtractor = colorExtractor;
         mStatusBarService = statusBarService;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mControlsComponent = controlsComponent;
         mControlsUiControllerOptional = controlsComponent.getControlsUiController();
         mIWindowManager = iWindowManager;
         mBackgroundExecutor = backgroundExecutor;
@@ -387,7 +391,8 @@
                     if (mDialog.mWalletViewController != null) {
                         mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
                     }
-                    if (!mDialog.isShowingControls() && shouldShowControls()) {
+                    if (!mDialog.isShowingControls()
+                            && mControlsComponent.getVisibility() == AVAILABLE) {
                         mDialog.showControls(mControlsUiControllerOptional.get());
                     }
                     if (unlocked) {
@@ -397,14 +402,15 @@
             }
         });
 
-        if (controlsComponent.getControlsListingController().isPresent()) {
-            controlsComponent.getControlsListingController().get()
+        if (mControlsComponent.getControlsListingController().isPresent()) {
+            mControlsComponent.getControlsListingController().get()
                     .addCallback(list -> {
                         mControlsServiceInfos = list;
                         // This callback may occur after the dialog has been shown. If so, add
                         // controls into the already visible space or show the lock msg if needed.
                         if (mDialog != null) {
-                            if (!mDialog.isShowingControls() && shouldShowControls()) {
+                            if (!mDialog.isShowingControls()
+                                    && mControlsComponent.getVisibility() == AVAILABLE) {
                                 mDialog.showControls(mControlsUiControllerOptional.get());
                             } else if (shouldShowLockMessage(mDialog)) {
                                 mDialog.showLockMessage();
@@ -704,7 +710,7 @@
 
         mDepthController.setShowingHomeControls(true);
         ControlsUiController uiController = null;
-        if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
+        if (mControlsComponent.getVisibility() == AVAILABLE) {
             uiController = mControlsUiControllerOptional.get();
         }
         ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
@@ -2687,26 +2693,24 @@
         return isPanelDebugModeEnabled(context);
     }
 
-    private boolean shouldShowControls() {
-        boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils
-                .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT;
-        return controlsAvailable()
-                && (mKeyguardStateController.isUnlocked() || showOnLockScreen);
-    }
-
     private boolean controlsAvailable() {
         return mDeviceProvisioned
-                && mControlsUiControllerOptional.isPresent()
-                && mControlsUiControllerOptional.get().getAvailable()
+                && mControlsComponent.isEnabled()
                 && !mControlsServiceInfos.isEmpty();
     }
 
     private boolean shouldShowLockMessage(ActionsDialog dialog) {
+        return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
+                || isWalletAvailableAfterUnlock(dialog);
+    }
+
+    // Temporary while we move items out of the power menu
+    private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
         boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                 == STRONG_AUTH_REQUIRED_AFTER_BOOT;
         return !mKeyguardStateController.isUnlocked()
                 && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
-                && (controlsAvailable() || dialog.isWalletViewAvailable());
+                && dialog.isWalletViewAvailable();
     }
 
     private void onPowerMenuLockScreenSettingsChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 1a0356c..01a353c 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -58,6 +58,14 @@
         mWallpaper = new ImageGLWallpaper(mProgram);
     }
 
+    /**
+     * @hide
+     * @return
+     */
+    public void useBitmap(Consumer<Bitmap> c) {
+        mTexture.use(c);
+    }
+
     @Override
     public boolean isWcgContent() {
         return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index d0070d8d..8c04143 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -289,7 +289,8 @@
         if (hasIndications()) {
             pw.println("    All messages:");
             for (int type : mIndicationMessages.keySet()) {
-                pw.println("        type=" + type + " message=" + mIndicationMessages.get(type));
+                pw.println("        type=" + type
+                        + " message=" + mIndicationMessages.get(type).getMessage());
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5a918d4..91cf710 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -67,6 +67,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.RemoteAnimationTarget;
@@ -306,6 +307,13 @@
      */
     private final SparseIntArray mLastSimStates = new SparseIntArray();
 
+    /**
+     * Indicates if a SIM card had the SIM PIN enabled during the initialization, before
+     * reaching the SIM_STATE_READY state. The flag is reset to false at SIM_STATE_READY.
+     * Index is the slotId - in case of multiple SIM cards.
+     */
+    private final SparseBooleanArray mSimWasLocked = new SparseBooleanArray();
+
     private boolean mDeviceInteractive;
     private boolean mGoingToSleep;
 
@@ -407,6 +415,7 @@
 
         @Override
         public void onUserSwitching(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
             // We need to force a reset of the views, since lockNow (called by
             // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
@@ -424,6 +433,7 @@
 
         @Override
         public void onUserSwitchComplete(int userId) {
+            if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
             if (userId != UserHandle.USER_SYSTEM) {
                 UserInfo info = UserManager.get(mContext).getUserInfo(userId);
                 // Don't try to dismiss if the user has Pin/Patter/Password set
@@ -477,10 +487,10 @@
                 }
             }
 
-            boolean simWasLocked;
+            boolean lastSimStateWasLocked;
             synchronized (KeyguardViewMediator.this) {
                 int lastState = mLastSimStates.get(slotId);
-                simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
+                lastSimStateWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED
                         || lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED);
                 mLastSimStates.append(slotId, simState);
             }
@@ -504,17 +514,19 @@
                         if (simState == TelephonyManager.SIM_STATE_ABSENT) {
                             // MVNO SIMs can become transiently NOT_READY when switching networks,
                             // so we should only lock when they are ABSENT.
-                            if (simWasLocked) {
+                            if (lastSimStateWasLocked) {
                                 if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
                                         + "previous state was locked. Reset the state.");
                                 resetStateLocked();
                             }
+                            mSimWasLocked.append(slotId, false);
                         }
                     }
                     break;
                 case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                 case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                     synchronized (KeyguardViewMediator.this) {
+                        mSimWasLocked.append(slotId, true);
                         if (!mShowing) {
                             if (DEBUG_SIM_STATES) Log.d(TAG,
                                     "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
@@ -541,9 +553,10 @@
                 case TelephonyManager.SIM_STATE_READY:
                     synchronized (KeyguardViewMediator.this) {
                         if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing);
-                        if (mShowing && simWasLocked) {
+                        if (mShowing && mSimWasLocked.get(slotId, false)) {
                             if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the "
-                                    + "previous state was locked. Reset the state.");
+                                    + "previously was locked. Reset the state.");
+                            mSimWasLocked.append(slotId, false);
                             resetStateLocked();
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 76281d8..a747edd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -29,7 +29,10 @@
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
@@ -61,7 +64,11 @@
 /**
  * Dagger Module providing {@link StatusBar}.
  */
-@Module(subcomponents = {KeyguardStatusViewComponent.class},
+@Module(subcomponents = {
+        KeyguardQsUserSwitchComponent.class,
+        KeyguardStatusBarViewComponent.class,
+        KeyguardStatusViewComponent.class,
+        KeyguardUserSwitcherComponent.class},
         includes = {FalsingModule.class})
 public class KeyguardModule {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index e8dba8f..c1db8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.log
 
 import android.util.Log
-import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.dagger.LogModule
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
@@ -58,7 +57,7 @@
  * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
  * the first letter of any of the previous.
  *
- * Buffers are provided by [LogModule].
+ * Buffers are provided by [LogModule]. Instances should be created using a [LogBufferFactory].
  *
  * @param name The name of this buffer
  * @param maxLogs The maximum number of messages to keep in memory at any one time, including the
@@ -77,10 +76,6 @@
     var frozen = false
         private set
 
-    fun attach(dumpManager: DumpManager) {
-        dumpManager.registerBuffer(name, this)
-    }
-
     /**
      * Logs a message to the log buffer
      *
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
new file mode 100644
index 0000000..0622df3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.android.systemui.log
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import javax.inject.Inject
+
+@SysUISingleton
+class LogBufferFactory @Inject constructor(
+    private val dumpManager: DumpManager,
+    private val logcatEchoTracker: LogcatEchoTracker
+) {
+    @JvmOverloads
+    fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer {
+        val buffer = LogBuffer(name, maxPoolSize, flexSize, logcatEchoTracker)
+        dumpManager.registerBuffer(name, buffer)
+        return buffer
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index fff185b..19193f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,8 +22,8 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.log.LogcatEchoTracker;
 import com.android.systemui.log.LogcatEchoTrackerDebug;
 import com.android.systemui.log.LogcatEchoTrackerProd;
@@ -40,96 +40,64 @@
     @Provides
     @SysUISingleton
     @DozeLog
-    public static LogBuffer provideDozeLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideDozeLogBuffer(LogBufferFactory factory) {
+        return factory.create("DozeLog", 100);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
     @NotificationLog
-    public static LogBuffer provideNotificationsLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifLog", 1000, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifLog", 1000);
     }
 
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
     @NotificationSectionLog
-    public static LogBuffer provideNotificationSectionLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifSectionLog", 1000);
     }
 
     /** Provides a logging buffer for all logs related to the data layer of notifications. */
     @Provides
     @SysUISingleton
     @NotifInteractionLog
-    public static LogBuffer provideNotifInteractionLogBuffer(
-            LogcatEchoTracker echoTracker,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideNotifInteractionLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifInteractionLog", 50);
     }
 
     /** Provides a logging buffer for all logs related to Quick Settings. */
     @Provides
     @SysUISingleton
     @QSLog
-    public static LogBuffer provideQuickSettingsLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
+        return factory.create("QSLog", 500);
     }
 
     /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
     @Provides
     @SysUISingleton
     @BroadcastDispatcherLog
-    public static LogBuffer provideBroadcastDispatcherLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) {
+        return factory.create("BroadcastDispatcherLog", 500);
     }
 
     /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
     @Provides
     @SysUISingleton
     @ToastLog
-    public static LogBuffer provideToastLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer provideToastLogBuffer(LogBufferFactory factory) {
+        return factory.create("ToastLog", 50);
     }
 
     /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
     @Provides
     @SysUISingleton
     @PrivacyLog
-    public static LogBuffer providePrivacyLogBuffer(
-            LogcatEchoTracker bufferFilter,
-            DumpManager dumpManager) {
-        LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
-        buffer.attach(dumpManager);
-        return buffer;
+    public static LogBuffer providePrivacyLogBuffer(LogBufferFactory factory) {
+        return factory.create("PrivacyLog", 100);
     }
 
     /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 9353526..a3ff375 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,7 +1,9 @@
 package com.android.systemui.media
 
+import android.animation.ArgbEvaluator
 import android.content.Context
 import android.content.Intent
+import android.content.res.ColorStateList
 import android.content.res.Configuration
 import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
 import android.util.Log
@@ -112,6 +114,9 @@
     private val visualStabilityCallback: VisualStabilityManager.Callback
     private var needsReordering: Boolean = false
     private var keysNeedRemoval = mutableSetOf<String>()
+    private var bgColor = getBackgroundColor()
+    private var fgColor = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.textColorPrimary).defaultColor
     private var isRtl: Boolean = false
         set(value) {
             if (value != field) {
@@ -147,7 +152,7 @@
         }
 
         override fun onUiModeChanged() {
-            // Only settings button needs to update for dark theme
+            recreatePlayers()
             inflateSettingsButton()
         }
     }
@@ -249,6 +254,11 @@
     }
 
     private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) {
+        data.actions.forEach {
+            it.icon?.setTintList(ColorStateList.valueOf(fgColor))
+        }
+        data.appIcon?.setTintList(ColorStateList.valueOf(fgColor))
+        val dataCopy = data.copy(backgroundColor = bgColor)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
         if (existingPlayer == null) {
             var newPlayer = mediaControlPanelFactory.get()
@@ -257,14 +267,14 @@
             val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT)
             newPlayer.view?.player?.setLayoutParams(lp)
-            newPlayer.bind(data, key)
+            newPlayer.bind(dataCopy, key)
             newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(key, data, newPlayer)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers()
         } else {
-            existingPlayer.bind(data, key)
-            MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
+            existingPlayer.bind(dataCopy, key)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer)
             if (visualStabilityManager.isReorderingAllowed) {
                 reorderAllPlayers()
             } else {
@@ -298,12 +308,27 @@
     }
 
     private fun recreatePlayers() {
+        bgColor = getBackgroundColor()
+
+        fgColor = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.textColorPrimary).defaultColor
+        pageIndicator.tintList = ColorStateList.valueOf(fgColor)
+
         MediaPlayerData.mediaData().forEach { (key, data) ->
             removePlayer(key, dismissMediaData = false)
             addOrUpdatePlayer(key = key, oldKey = null, data = data)
         }
     }
 
+    private fun getBackgroundColor(): Int {
+        val themeAccent = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.colorAccent).defaultColor
+        val themeBackground = com.android.settingslib.Utils.getColorAttr(context,
+                com.android.internal.R.attr.colorBackground).defaultColor
+        // Simulate transparency - cannot be actually transparent because of lockscreen
+        return ArgbEvaluator().evaluate(0.25f, themeBackground, themeAccent) as Int
+    }
+
     private fun updatePageIndicator() {
         val numPages = mediaContent.getChildCount()
         pageIndicator.setNumPages(numPages)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3629d4d..55c55b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -25,7 +25,6 @@
 import android.content.IntentFilter
 import android.graphics.Bitmap
 import android.graphics.Canvas
-import android.graphics.Color
 import android.graphics.ImageDecoder
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
@@ -38,7 +37,6 @@
 import android.service.notification.StatusBarNotification
 import android.text.TextUtils
 import android.util.Log
-import com.android.internal.graphics.ColorUtils
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -48,7 +46,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.util.Assert
 import com.android.systemui.util.Utils
@@ -68,10 +65,6 @@
 
 private const val TAG = "MediaDataManager"
 private const val DEBUG = true
-private const val DEFAULT_LUMINOSITY = 0.25f
-private const val LUMINOSITY_THRESHOLD = 0.05f
-private const val SATURATION_MULTIPLIER = 0.8f
-const val DEFAULT_COLOR = Color.DKGRAY
 
 private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
         emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@@ -110,6 +103,11 @@
     private val useQsMediaPlayer: Boolean
 ) : Dumpable {
 
+    private val themeText = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.textColorPrimary).defaultColor
+    private val bgColor = com.android.settingslib.Utils.getColorAttr(context,
+            com.android.internal.R.attr.colorBackground).defaultColor
+
     // Internal listeners are part of the internal pipeline. External listeners (those registered
     // with [MediaDeviceManager.addListener]) receive events after they have propagated through
     // the internal pipeline.
@@ -395,7 +393,6 @@
         } else {
             null
         }
-        val bgColor = artworkBitmap?.let { computeBackgroundColor(it) } ?: DEFAULT_COLOR
 
         val mediaAction = getResumeMediaAction(resumeAction)
         foregroundExecutor.execute {
@@ -449,7 +446,6 @@
                 }
             }
         }
-        val bgColor = computeBackgroundColor(artworkBitmap)
 
         // App name
         val builder = Notification.Builder.recoverBuilder(context, notif)
@@ -506,7 +502,7 @@
                     Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
                 } else {
                     action.getIcon()
-                }
+                }.setTint(themeText)
                 val mediaAction = MediaAction(
                         mediaActionIcon,
                         runnable,
@@ -589,38 +585,9 @@
         }
     }
 
-    private fun computeBackgroundColor(artworkBitmap: Bitmap?): Int {
-        var color = Color.WHITE
-        if (artworkBitmap != null && artworkBitmap.width > 1 && artworkBitmap.height > 1) {
-            // If we have valid art, get colors from that
-            val p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
-                    .generate()
-            val swatch = MediaNotificationProcessor.findBackgroundSwatch(p)
-            color = swatch.rgb
-        } else {
-            return DEFAULT_COLOR
-        }
-        // Adapt background color, so it's always subdued and text is legible
-        val tmpHsl = floatArrayOf(0f, 0f, 0f)
-        ColorUtils.colorToHSL(color, tmpHsl)
-
-        val l = tmpHsl[2]
-        // Colors with very low luminosity can have any saturation. This means that changing the
-        // luminosity can make a black become red. Let's remove the saturation of very light or
-        // very dark colors to avoid this issue.
-        if (l < LUMINOSITY_THRESHOLD || l > 1f - LUMINOSITY_THRESHOLD) {
-            tmpHsl[1] = 0f
-        }
-        tmpHsl[1] *= SATURATION_MULTIPLIER
-        tmpHsl[2] = DEFAULT_LUMINOSITY
-
-        color = ColorUtils.HSLToColor(tmpHsl)
-        return color
-    }
-
     private fun getResumeMediaAction(action: Runnable): MediaAction {
         return MediaAction(
-            Icon.createWithResource(context, R.drawable.lb_ic_play),
+            Icon.createWithResource(context, R.drawable.lb_ic_play).setTint(themeText),
             action,
             context.getString(R.string.controls_media_resume)
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 4c96de2..553b6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -163,7 +163,8 @@
         }
 
         @Override
-        public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
+        public void setPlaybackProperties(IBinder token, float volume, boolean looping,
+                boolean hapticGeneratorEnabled) {
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
@@ -171,6 +172,7 @@
             if (client != null) {
                 client.mRingtone.setVolume(volume);
                 client.mRingtone.setLooping(looping);
+                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
             }
             // else no client for token when setting playback properties but will be set at play()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
new file mode 100644
index 0000000..e7458a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people;
+
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.app.people.PeopleSpaceTile;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.systemui.shared.system.PeopleProviderUtils;
+
+/** API that returns a People Tile preview. */
+public class PeopleProvider extends ContentProvider {
+
+    LauncherApps mLauncherApps;
+    IPeopleManager mPeopleManager;
+
+    private static final String TAG = "PeopleProvider";
+    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+    private static final String EMPTY_STRING = "";
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (!doesCallerHavePermission()) {
+            String callingPackage = getCallingPackage();
+            Log.w(TAG, "API not accessible to calling package: " + callingPackage);
+            throw new SecurityException("API not accessible to calling package: " + callingPackage);
+        }
+        if (!PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD.equals(method)) {
+            Log.w(TAG, "Invalid method");
+            throw new IllegalArgumentException("Invalid method");
+        }
+
+        // If services are not set as mocks in tests, fetch them now.
+        mPeopleManager = mPeopleManager != null ? mPeopleManager
+                : IPeopleManager.Stub.asInterface(
+                        ServiceManager.getService(Context.PEOPLE_SERVICE));
+        mLauncherApps = mLauncherApps != null ? mLauncherApps
+                : getContext().getSystemService(LauncherApps.class);
+
+        if (mPeopleManager == null || mLauncherApps == null) {
+            Log.w(TAG, "Null system managers");
+            return null;
+        }
+
+        if (extras == null) {
+            Log.w(TAG, "Extras can't be null");
+            throw new IllegalArgumentException("Extras can't be null");
+        }
+
+        String shortcutId = extras.getString(
+                PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, EMPTY_STRING);
+        String packageName = extras.getString(
+                PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, EMPTY_STRING);
+        UserHandle userHandle = extras.getParcelable(
+                PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE);
+        if (shortcutId.isEmpty()) {
+            Log.w(TAG, "Invalid shortcut id");
+            throw new IllegalArgumentException("Invalid shortcut id");
+        }
+
+        if (packageName.isEmpty()) {
+            Log.w(TAG, "Invalid package name");
+            throw new IllegalArgumentException("Invalid package name");
+        }
+        if (userHandle == null) {
+            Log.w(TAG, "Null user handle");
+            throw new IllegalArgumentException("Null user handle");
+        }
+
+        ConversationChannel channel;
+        try {
+            channel = mPeopleManager.getConversation(
+                    packageName, userHandle.getIdentifier(), shortcutId);
+        } catch (Exception e) {
+            Log.w(TAG, "Exception getting tiles: " + e);
+            return null;
+        }
+        PeopleSpaceTile tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
+
+        if (tile == null) {
+            if (DEBUG) Log.i(TAG, "No tile was returned");
+            return null;
+        }
+
+        if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
+        RemoteViews view = PeopleSpaceUtils.createRemoteViews(getContext(), tile, 0);
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS, view);
+        return bundle;
+    }
+
+    private boolean doesCallerHavePermission() {
+        return getContext().checkPermission(
+                    PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+                    Binder.getCallingPid(), Binder.getCallingUid())
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        throw new IllegalArgumentException("Invalid method");
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new IllegalArgumentException("Invalid method");
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues initialValues) {
+        throw new IllegalArgumentException("Invalid method");
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new IllegalArgumentException("Invalid method");
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new IllegalArgumentException("Invalid method");
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 580cbcf..c67aef6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -37,9 +37,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Shows the user their tiles for their priority People (go/live-status).
  */
@@ -54,10 +57,17 @@
     private LauncherApps mLauncherApps;
     private Context mContext;
     private AppWidgetManager mAppWidgetManager;
+    private NotificationEntryManager mNotificationEntryManager;
     private int mAppWidgetId;
     private boolean mShowSingleConversation;
     private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
 
+    @Inject
+    public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) {
+        super();
+        mNotificationEntryManager = notificationEntryManager;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -91,8 +101,8 @@
      */
     private void setTileViewsWithPriorityConversations() {
         try {
-            List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(
-                    mContext, mNotificationManager, mPeopleManager, mLauncherApps);
+            List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
+                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
             for (PeopleSpaceTile tile : tiles) {
                 PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, mPeopleSpaceLayout,
                         tile.getId());
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 994dc6d..7eb1fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -49,6 +49,7 @@
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -60,9 +61,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.ArrayUtils;
 import com.android.settingslib.utils.ThreadUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.text.SimpleDateFormat;
 import java.time.Duration;
@@ -137,7 +141,7 @@
     /** Returns a list of map entries corresponding to user's conversations. */
     public static List<PeopleSpaceTile> getTiles(
             Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
-            LauncherApps launcherApps)
+            LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
             throws Exception {
         boolean showOnlyPriority = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 1;
@@ -173,6 +177,8 @@
                     getSortedTiles(peopleManager, launcherApps, mergedStream);
             tiles.addAll(recentTiles);
         }
+
+        tiles = augmentTilesFromVisibleNotifications(tiles, notificationEntryManager);
         return tiles;
     }
 
@@ -258,7 +264,8 @@
                             ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
                             IPeopleManager.Stub.asInterface(
                                     ServiceManager.getService(Context.PEOPLE_SERVICE)),
-                            context.getSystemService(LauncherApps.class));
+                            context.getSystemService(LauncherApps.class),
+                            Dependency.get(NotificationEntryManager.class));
             Optional<PeopleSpaceTile> entry = tiles.stream().filter(
                     e -> e.getId().equals(shortcutId)).findFirst();
             if (entry.isPresent()) {
@@ -339,6 +346,41 @@
                 && storedUserId == userId;
     }
 
+    static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(List<PeopleSpaceTile> tiles,
+            NotificationEntryManager notificationEntryManager) {
+        if (notificationEntryManager == null) {
+            Log.w(TAG, "NotificationEntryManager is null");
+            return tiles;
+        }
+        Map<String, NotificationEntry> visibleNotifications = notificationEntryManager
+                .getVisibleNotifications()
+                .stream()
+                .filter(entry -> entry.getRanking() != null
+                        && entry.getRanking().getConversationShortcutInfo() != null)
+                .collect(Collectors.toMap(PeopleSpaceUtils::getKey, e -> e));
+        if (DEBUG) {
+            Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size());
+        }
+        return tiles
+                .stream()
+                .map(entry -> augmentTileFromVisibleNotifications(entry, visibleNotifications))
+                .collect(Collectors.toList());
+    }
+
+    static PeopleSpaceTile augmentTileFromVisibleNotifications(PeopleSpaceTile tile,
+            Map<String, NotificationEntry> visibleNotifications) {
+        String shortcutId = tile.getId();
+        String packageName = tile.getPackageName();
+        int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+        String key = getKey(shortcutId, packageName, userId);
+        if (!visibleNotifications.containsKey(key)) {
+            if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key);
+            return tile;
+        }
+        if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key);
+        return augmentTileFromNotification(tile, visibleNotifications.get(key).getSbn());
+    }
+
     /**
      * If incoming notification changed tile, store the changes in the tile options.
      */
@@ -355,17 +397,7 @@
         }
         if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
             if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId);
-            Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
-            if (message == null) {
-                if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
-                return;
-            }
-            storedTile = storedTile
-                    .toBuilder()
-                    .setNotificationKey(sbn.getKey())
-                    .setNotificationContent(message.getText())
-                    .setNotificationDataUri(message.getDataUri())
-                    .build();
+            storedTile = augmentTileFromNotification(storedTile, sbn);
         } else {
             if (DEBUG) {
                 Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId);
@@ -380,6 +412,21 @@
         updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile);
     }
 
+    static PeopleSpaceTile augmentTileFromNotification(PeopleSpaceTile tile,
+            StatusBarNotification sbn) {
+        Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn);
+        if (message == null) {
+            if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping.");
+            return tile;
+        }
+        return tile
+                .toBuilder()
+                .setNotificationKey(sbn.getKey())
+                .setNotificationContent(message.getText())
+                .setNotificationDataUri(message.getDataUri())
+                .build();
+    }
+
     private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId,
             PeopleSpaceTile tile) {
         if (tile == null) {
@@ -392,7 +439,7 @@
     }
 
     /** Creates a {@link RemoteViews} for {@code tile}. */
-    private static RemoteViews createRemoteViews(Context context,
+    public static RemoteViews createRemoteViews(Context context,
             PeopleSpaceTile tile, int appWidgetId) {
         RemoteViews views;
         if (tile.getNotificationKey() != null) {
@@ -409,6 +456,9 @@
             PeopleSpaceTile tile, int appWidgetId) {
         try {
             views.setTextViewText(R.id.name, tile.getUserName().toString());
+            views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
+            views.setBoolean(R.id.content_background, "setClipToOutline", true);
+
             views.setImageViewBitmap(
                     R.id.package_icon,
                     PeopleSpaceUtils.convertDrawableToBitmap(
@@ -416,8 +466,6 @@
                                     tile.getPackageName())
                     )
             );
-            views.setImageViewIcon(R.id.person_icon, tile.getUserIcon());
-            views.setBoolean(R.id.content_background, "setClipToOutline", true);
 
             Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
             activityIntent.addFlags(
@@ -566,6 +614,22 @@
                 .collect(Collectors.toList());
     }
 
+    /** Returns {@code PeopleSpaceTile} based on provided  {@ConversationChannel}. */
+    public static PeopleSpaceTile getTile(ConversationChannel channel, LauncherApps launcherApps) {
+        if (channel == null) {
+            Log.i(TAG, "ConversationChannel is null");
+            return null;
+        }
+
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build();
+        if (!PeopleSpaceUtils.shouldKeepConversation(tile)) {
+            Log.i(TAG, "PeopleSpaceTile is not valid");
+            return null;
+        }
+
+        return tile;
+    }
+
     /** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */
     private static Long getLastInteraction(IPeopleManager peopleManager,
             PeopleSpaceTile tile) {
@@ -655,7 +719,7 @@
      * </li>
      */
     public static boolean shouldKeepConversation(PeopleSpaceTile tile) {
-        return tile != null && tile.getUserName().length() != 0;
+        return tile != null && !TextUtils.isEmpty(tile.getUserName());
     }
 
     private static boolean hasBirthdayStatus(PeopleSpaceTile tile, Context context) {
@@ -746,8 +810,7 @@
     private static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
             Context context, int appWidgetId, PeopleSpaceTile tile) {
         updateAppWidgetOptions(appWidgetManager, appWidgetId, tile);
-        RemoteViews views = createRemoteViews(context,
-                tile, appWidgetId);
+        RemoteViews views = createRemoteViews(context, tile, appWidgetId);
         appWidgetManager.updateAppWidget(appWidgetId, views);
     }
 
@@ -792,6 +855,16 @@
         return lookupKeysWithBirthdaysToday;
     }
 
+    static String getKey(NotificationEntry entry) {
+        if (entry.getRanking() == null || entry.getRanking().getConversationShortcutInfo() == null
+                || entry.getSbn() == null || entry.getSbn().getUser() == null) {
+            return null;
+        }
+        return getKey(entry.getRanking().getConversationShortcutInfo().getId(),
+                entry.getSbn().getPackageName(),
+                entry.getSbn().getUser().getIdentifier());
+    }
+
     /**
      * Returns the uniquely identifying key for the conversation.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index bee54e4..bee9889 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -135,12 +135,14 @@
         try {
             String sbnShortcutId = sbn.getShortcutId();
             if (sbnShortcutId == null) {
+                if (DEBUG) Log.d(TAG, "Sbn shortcut id is null");
                 return;
             }
             int[] widgetIds = mAppWidgetService.getAppWidgetIds(
                     new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
             );
             if (widgetIds.length == 0) {
+                Log.d(TAG, "No app widget ids returned");
                 return;
             }
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -148,6 +150,7 @@
             String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId);
             Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
             if (storedWidgetIds.isEmpty()) {
+                Log.d(TAG, "No stored widget ids");
                 return;
             }
             for (String widgetIdString : storedWidgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index fb33aff..80794cb 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -28,9 +28,11 @@
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.people.PeopleSpaceTileView;
 import com.android.systemui.people.PeopleSpaceUtils;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -42,6 +44,7 @@
 
     private IPeopleManager mPeopleManager;
     private INotificationManager mNotificationManager;
+    private NotificationEntryManager mNotificationEntryManager;
     private PackageManager mPackageManager;
     private LauncherApps mLauncherApps;
     private List<PeopleSpaceTile> mTiles = new ArrayList<>();
@@ -56,6 +59,7 @@
         if (DEBUG) Log.d(TAG, "onCreate called");
         mNotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mPackageManager = mContext.getPackageManager();
         mPeopleManager = IPeopleManager.Stub.asInterface(
                 ServiceManager.getService(Context.PEOPLE_SERVICE));
@@ -70,7 +74,7 @@
     private void setTileViewsWithPriorityConversations() {
         try {
             mTiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
-                    mPeopleManager, mLauncherApps);
+                    mPeopleManager, mLauncherApps, mNotificationEntryManager);
         } catch (Exception e) {
             Log.e(TAG, "Couldn't retrieve conversations", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 870e714..bdd37fc 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -16,11 +16,11 @@
 
 import android.content.Context
 import android.util.AttributeSet
-import android.view.Gravity
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
+import com.android.settingslib.Utils
 import com.android.systemui.R
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -30,26 +30,13 @@
     defStyleRes: Int = 0
 ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
 
-    private val iconMarginExpanded = context.resources.getDimensionPixelSize(
-                    R.dimen.ongoing_appops_chip_icon_margin_expanded)
-    private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
-                    R.dimen.ongoing_appops_chip_icon_margin_collapsed)
-    private val iconSize =
-            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
-    private val iconColor = context.resources.getColor(
-            R.color.status_bar_clock_color, context.theme)
-    private val sidePadding =
-            context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
-    private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
+    private var iconMargin = 0
+    private var iconSize = 0
+    private var iconColor = 0
+    private var defaultBackgroundColor = 0
+    private var cameraBackgroundColor = 0
+
     private lateinit var iconsContainer: LinearLayout
-    private lateinit var back: FrameLayout
-    var expanded = false
-        set(value) {
-            if (value != field) {
-                field = value
-                updateView(PrivacyChipBuilder(context, privacyList))
-            }
-        }
 
     var privacyList = emptyList<PrivacyItem>()
         set(value) {
@@ -60,15 +47,13 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        back = requireViewById(R.id.background)
         iconsContainer = requireViewById(R.id.icons_container)
+
+        updateResources()
     }
 
     // Should only be called if the builder icons or app changed
     private fun updateView(builder: PrivacyChipBuilder) {
-        back.background = if (expanded) backgroundDrawable else null
-        val padding = if (expanded) sidePadding else 0
-        back.setPaddingRelative(padding, 0, padding, 0)
         fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {
             iconsContainer.removeAllViews()
             chipBuilder.generateIcons().forEachIndexed { i, it ->
@@ -81,7 +66,7 @@
                 iconsContainer.addView(image, iconSize, iconSize)
                 if (i != 0) {
                     val lp = image.layoutParams as MarginLayoutParams
-                    lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
+                    lp.marginStart = iconMargin
                     image.layoutParams = lp
                 }
             }
@@ -90,10 +75,11 @@
         if (!privacyList.isEmpty()) {
             generateContentDescription(builder)
             setIcons(builder, iconsContainer)
-            val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams
-            lp.gravity = Gravity.CENTER_VERTICAL or
-                    (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END)
-            iconsContainer.layoutParams = lp
+            if (builder.types.contains(PrivacyType.TYPE_CAMERA)) {
+                iconsContainer.background.setTint(cameraBackgroundColor)
+            } else {
+                iconsContainer.background.setTint(defaultBackgroundColor)
+            }
         } else {
             iconsContainer.removeAllViews()
         }
@@ -105,4 +91,20 @@
         contentDescription = context.getString(
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
+
+    private fun updateResources() {
+        iconMargin = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+        iconSize = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+        iconColor =
+                Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+        defaultBackgroundColor = context.getColor(R.color.privacy_circle_microphone_location)
+        cameraBackgroundColor = context.getColor(R.color.privacy_circle_camera)
+
+        val padding = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+        iconsContainer.setPaddingRelative(padding, 0, padding, 0)
+        iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index d248ab5..c8edaec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -362,7 +362,7 @@
         if(view == parent || view == null) return;
         // Ignore tile pages as they can have some offset we don't want to take into account in
         // RTL.
-        if (!(view instanceof PagedTileLayout.TilePage)) {
+        if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) {
             loc1[0] += view.getLeft();
             loc1[1] += view.getTop();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 619729e..9967936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -72,6 +72,7 @@
     private boolean mFullyExpanded;
     private QuickStatusBarHeader mHeader;
     private boolean mTriggeredExpand;
+    private boolean mShouldAnimate;
     private int mOpenX;
     private int mOpenY;
     private boolean mAnimatingOpen;
@@ -108,16 +109,6 @@
         updateDetailText();
 
         mClipper = new QSDetailClipper(this);
-
-        final OnClickListener doneListener = new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                announceForAccessibility(
-                        mContext.getString(R.string.accessibility_desc_quick_settings));
-                mQsPanelController.closeDetail();
-            }
-        };
-        mDetailDoneButton.setOnClickListener(doneListener);
     }
 
     /** */
@@ -169,6 +160,7 @@
     public void handleShowingDetail(final DetailAdapter adapter, int x, int y,
             boolean toggleQs) {
         final boolean showingDetail = adapter != null;
+        final boolean wasShowingDetail = mDetailAdapter != null;
         setClickable(showingDetail);
         if (showingDetail) {
             setupDetailHeader(adapter);
@@ -178,6 +170,7 @@
             } else {
                 mTriggeredExpand = false;
             }
+            mShouldAnimate = adapter.shouldAnimate();
             mOpenX = x;
             mOpenY = y;
         } else {
@@ -190,10 +183,10 @@
             }
         }
 
-        boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
-        if (!visibleDiff && mDetailAdapter == adapter) return;  // already in right state
-        AnimatorListener listener = null;
-        if (adapter != null) {
+        boolean visibleDiff = wasShowingDetail != showingDetail;
+        if (!visibleDiff && !wasShowingDetail) return;  // already in right state
+        AnimatorListener listener;
+        if (showingDetail) {
             int viewCacheIndex = adapter.getMetricsCategory();
             View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
                     mDetailContent);
@@ -213,7 +206,7 @@
             listener = mHideGridContentWhenDone;
             setVisibility(View.VISIBLE);
         } else {
-            if (mDetailAdapter != null) {
+            if (wasShowingDetail) {
                 Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
                 mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
             }
@@ -227,7 +220,15 @@
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
 
-        animateDetailVisibleDiff(x, y, visibleDiff, listener);
+        if (mShouldAnimate) {
+            animateDetailVisibleDiff(x, y, visibleDiff, listener);
+        } else {
+            if (showingDetail) {
+                showImmediately();
+            } else {
+                hideImmediately();
+            }
+        }
     }
 
     protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
@@ -245,6 +246,17 @@
         }
     }
 
+    void showImmediately() {
+        setVisibility(VISIBLE);
+        mClipper.cancelAnimator();
+        mClipper.showBackground();
+    }
+
+    public void hideImmediately() {
+        mClipper.cancelAnimator();
+        setVisibility(View.GONE);
+    }
+
     protected void setupDetailFooter(DetailAdapter adapter) {
         final Intent settingsIntent = adapter.getSettingsIntent();
         mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
@@ -255,6 +267,13 @@
             Dependency.get(ActivityStarter.class)
                     .postStartActivityDismissingKeyguard(settingsIntent, 0);
         });
+        mDetailDoneButton.setOnClickListener(v -> {
+            announceForAccessibility(
+                    mContext.getString(R.string.accessibility_desc_quick_settings));
+            if (!adapter.onDoneButtonClicked()) {
+                mQsPanelController.closeDetail();
+            }
+        });
     }
 
     protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
index 7d87e174..b50af00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java
@@ -39,7 +39,7 @@
     /** Show the supplied DetailAdapter in the Quick Settings. */
     public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
         if (mQsPanelController != null) {
-            mQsPanelController.showDetailDapater(detailAdapter, x, y);
+            mQsPanelController.showDetailAdapter(detailAdapter, x, y);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f56a890..fcb35e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.tuner.TunerService;
 
@@ -92,9 +93,10 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSlider.Factory brightnessSliderFactory,
-            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
-                metricsLogger, uiEventLogger, qsLogger, dumpManager);
+                metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
         mQsCustomizerController = qsCustomizerController;
@@ -309,7 +311,7 @@
     }
 
     /** */
-    public void showDetailDapater(DetailAdapter detailAdapter, int x, int y) {
+    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 b02799f..9426e71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -34,6 +34,8 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.Utils;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -63,6 +65,7 @@
     private final UiEventLogger mUiEventLogger;
     private final QSLogger mQSLogger;
     private final DumpManager mDumpManager;
+    private final FeatureFlags mFeatureFlags;
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
 
     private int mLastOrientation;
@@ -93,11 +96,18 @@
 
     private boolean mUsingHorizontalLayout;
 
-    protected QSPanelControllerBase(T view, QSTileHost host,
+    protected QSPanelControllerBase(
+            T view,
+            QSTileHost host,
             QSCustomizerController qsCustomizerController,
-            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager) {
+            @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
+            MediaHost mediaHost,
+            MetricsLogger metricsLogger,
+            UiEventLogger uiEventLogger,
+            QSLogger qsLogger,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags
+    ) {
         super(view);
         mHost = host;
         mQsCustomizerController = qsCustomizerController;
@@ -107,6 +117,7 @@
         mUiEventLogger = uiEventLogger;
         mQSLogger = qsLogger;
         mDumpManager = dumpManager;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -334,9 +345,12 @@
     }
 
     boolean shouldUseHorizontalLayout() {
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources()))  {
+            return false;
+        }
         return mUsingMediaPlayer && mMediaHost.getVisible()
-                && getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+                    && getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_LANDSCAPE;
     }
 
     private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a0db200..383e932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -30,6 +30,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -57,9 +58,11 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
+            FeatureFlags featureFlags
+    ) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
-                uiEventLogger, qsLogger, dumpManager);
+                uiEventLogger, qsLogger, dumpManager, featureFlags);
         mUseSideLabels = qsLabelsFlag;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4248cf2..87252ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -29,7 +29,6 @@
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.view.ContextThemeWrapper;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.ViewGroup;
@@ -48,7 +47,6 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.privacy.OngoingPrivacyChip;
@@ -75,8 +73,6 @@
     protected QuickQSPanel mHeaderQsPanel;
     private TouchAnimator mStatusIconsAlphaAnimator;
     private TouchAnimator mHeaderTextContainerAlphaAnimator;
-    private TouchAnimator mPrivacyChipAlphaAnimator;
-    private DualToneHandler mDualToneHandler;
 
     private View mSystemIconsView;
     private View mQuickQsStatusIcons;
@@ -111,8 +107,6 @@
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mDualToneHandler = new DualToneHandler(
-                new ContextThemeWrapper(context, R.style.QSHeaderTheme));
     }
 
     @Override
@@ -150,10 +144,8 @@
     }
 
     void onAttach(TintedIconManager iconManager) {
-        int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
-                android.R.attr.colorForeground);
-        float intensity = getColorIntensity(colorForeground);
-        int fillColor = mDualToneHandler.getSingleColor(intensity);
+        int fillColor = Utils.getColorAttrDefaultColor(getContext(),
+                android.R.attr.textColorPrimary);
 
         // Set the correct tint for the status icons so they contrast
         iconManager.setTint(fillColor);
@@ -272,19 +264,16 @@
 
         int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
         if (textColor != mTextColorPrimary) {
+            int textColorSecondary = Utils.getColorAttrDefaultColor(mContext,
+                    android.R.attr.textColorSecondary);
             mTextColorPrimary = textColor;
             mClockView.setTextColor(textColor);
-
-            float intensity = getColorIntensity(textColor);
-            int fillColor = mDualToneHandler.getSingleColor(intensity);
-
-            Rect tintArea = new Rect(0, 0, 0, 0);
-            mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
+            mBatteryRemainingIcon.updateColors(mTextColorPrimary, textColorSecondary,
+                    mTextColorPrimary);
         }
 
         updateStatusIconAlphaAnimator();
         updateHeaderTextContainerAlphaAnimator();
-        updatePrivacyChipAlphaAnimator();
     }
 
     private void updateStatusIconAlphaAnimator() {
@@ -299,12 +288,6 @@
                 .build();
     }
 
-    private void updatePrivacyChipAlphaAnimator() {
-        mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
-                .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
-                .build();
-    }
-
     /** */
     public void setExpanded(boolean expanded, QuickQSPanelController quickQSPanelController) {
         if (mExpanded == expanded) return;
@@ -344,10 +327,6 @@
                 mHeaderTextContainerView.setVisibility(INVISIBLE);
             }
         }
-        if (mPrivacyChipAlphaAnimator != null) {
-            mPrivacyChip.setExpanded(expansionFraction > 0.5);
-            mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
-        }
 
         mKeyguardExpansionFraction = keyguardExpansionFraction;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 7d8d86f..ae0b5d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -28,9 +28,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.Objects;
 
@@ -40,10 +38,8 @@
     private TextView mCarrierText;
     private ImageView mMobileSignal;
     private ImageView mMobileRoaming;
-    private DualToneHandler mDualToneHandler;
-    private ColorStateList mColorForegroundStateList;
-    private float mColorForegroundIntensity;
     private CellSignalState mLastSignalState;
+    private boolean mProviderModel;
 
     public QSCarrier(Context context) {
         super(context);
@@ -64,21 +60,20 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mDualToneHandler = new DualToneHandler(getContext());
-        mMobileGroup = findViewById(R.id.mobile_combo);
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
-            mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+            mProviderModel = true;
         } else {
-            mMobileRoaming = findViewById(R.id.mobile_roaming);
+            mProviderModel = false;
         }
+        mMobileGroup = findViewById(R.id.mobile_combo);
+        mMobileRoaming = findViewById(R.id.mobile_roaming);
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
-        mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
-
-        int colorForeground = Utils.getColorAttrDefaultColor(mContext,
-                android.R.attr.colorForeground);
-        mColorForegroundStateList = ColorStateList.valueOf(colorForeground);
-        mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
+        if (mProviderModel) {
+            mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
+        } else {
+            mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+        }
     }
 
     /**
@@ -92,26 +87,31 @@
         mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
         if (state.visible) {
             mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-            ColorStateList colorStateList = ColorStateList.valueOf(
-                    mDualToneHandler.getSingleColor(mColorForegroundIntensity));
+            ColorStateList colorStateList = Utils.getColorAttr(mContext,
+                    android.R.attr.textColorPrimary);
             mMobileRoaming.setImageTintList(colorStateList);
             mMobileSignal.setImageTintList(colorStateList);
-            mMobileSignal.setImageLevel(state.mobileSignalIconId);
 
-            StringBuilder contentDescription = new StringBuilder();
-            if (state.contentDescription != null) {
-                contentDescription.append(state.contentDescription).append(", ");
+            if (mProviderModel) {
+                mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
+                mMobileSignal.setContentDescription(state.contentDescription);
+            } else {
+                mMobileSignal.setImageLevel(state.mobileSignalIconId);
+                StringBuilder contentDescription = new StringBuilder();
+                if (state.contentDescription != null) {
+                    contentDescription.append(state.contentDescription).append(", ");
+                }
+                if (state.roaming) {
+                    contentDescription
+                            .append(mContext.getString(R.string.data_connection_roaming))
+                            .append(", ");
+                }
+                // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+                if (hasValidTypeContentDescription(state.typeContentDescription)) {
+                    contentDescription.append(state.typeContentDescription);
+                }
+                mMobileSignal.setContentDescription(contentDescription);
             }
-            if (state.roaming) {
-                contentDescription
-                        .append(mContext.getString(R.string.data_connection_roaming))
-                        .append(", ");
-            }
-            // TODO: show mobile data off/no internet text for 5 seconds before carrier text
-            if (hasValidTypeContentDescription(state.typeContentDescription)) {
-                contentDescription.append(state.typeContentDescription);
-            }
-            mMobileSignal.setContentDescription(contentDescription);
         }
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 77200cc..aa6bbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -19,6 +19,7 @@
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
 
 import android.annotation.MainThread;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
@@ -26,13 +27,17 @@
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
+import com.android.settingslib.AccessibilityContentDescriptions;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -53,7 +58,7 @@
     private final ActivityStarter mActivityStarter;
     private final Handler mBgHandler;
     private final NetworkController mNetworkController;
-    private final CarrierTextController mCarrierTextController;
+    private final CarrierTextManager mCarrierTextManager;
     private final TextView mNoSimTextView;
     private final H mMainHandler;
     private final Callback mCallback;
@@ -62,6 +67,9 @@
             new CellSignalState[SIM_SLOTS];
     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
     private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+    private int[] mLastSignalLevel = new int[SIM_SLOTS];
+    private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
+    private final boolean mProviderModel;
 
     private final NetworkController.SignalCallback mSignalCallback =
             new NetworkController.SignalCallback() {
@@ -72,6 +80,9 @@
                         CharSequence typeContentDescription,
                         CharSequence typeContentDescriptionHtml, CharSequence description,
                         boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+                    if (mProviderModel) {
+                        return;
+                    }
                     int slotIndex = getSlotIndex(subId);
                     if (slotIndex >= SIM_SLOTS) {
                         Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
@@ -92,6 +103,46 @@
                 }
 
                 @Override
+                public void setCallIndicator(NetworkController.IconState statusIcon, int subId) {
+                    if (!mProviderModel) {
+                        return;
+                    }
+                    int slotIndex = getSlotIndex(subId);
+                    if (slotIndex >= SIM_SLOTS) {
+                        Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+                        return;
+                    }
+                    if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+                        return;
+                    }
+                    if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+                        if (statusIcon.visible) {
+                            mInfos[slotIndex] = new CellSignalState(true,
+                                    statusIcon.icon, statusIcon.contentDescription, "", false);
+                        } else {
+                            // Whenever the no Calling & SMS state is cleared, switched to the last
+                            // known call strength icon.
+                            mInfos[slotIndex] = new CellSignalState(
+                                    true, mLastSignalLevel[slotIndex],
+                                    mLastSignalLevelDescription[slotIndex], "", false);
+                        }
+                        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                    } else {
+                        mLastSignalLevel[slotIndex] = statusIcon.icon;
+                        mLastSignalLevelDescription[slotIndex] = statusIcon.contentDescription;
+                        // Only Shows the call strength icon when the no Calling & SMS icon is not
+                        // shown.
+                        if (mInfos[slotIndex].mobileSignalIconId
+                                != R.drawable.ic_qs_no_calling_sms) {
+                            mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
+                                    statusIcon.contentDescription, "", false);
+                            mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+                        }
+                    }
+                }
+
+                @Override
                 public void setNoSims(boolean hasNoSims, boolean simDetected) {
                     if (hasNoSims) {
                         for (int i = 0; i < SIM_SLOTS; i++) {
@@ -102,7 +153,7 @@
                 }
             };
 
-    private static class Callback implements CarrierTextController.CarrierTextCallback {
+    private static class Callback implements CarrierTextManager.CarrierTextCallback {
         private H mHandler;
 
         Callback(H handler) {
@@ -110,7 +161,7 @@
         }
 
         @Override
-        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        public void updateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
             mHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
         }
     }
@@ -118,11 +169,16 @@
     private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
             @Background Handler bgHandler, @Main Looper mainLooper,
             NetworkController networkController,
-            CarrierTextController.Builder carrierTextControllerBuilder) {
+            CarrierTextManager.Builder carrierTextManagerBuilder, Context context) {
+        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+            mProviderModel = true;
+        } else {
+            mProviderModel = false;
+        }
         mActivityStarter = activityStarter;
         mBgHandler = bgHandler;
         mNetworkController = networkController;
-        mCarrierTextController = carrierTextControllerBuilder
+        mCarrierTextManager = carrierTextManagerBuilder
                 .setShowAirplaneMode(false)
                 .setShowMissingSim(false)
                 .build();
@@ -140,7 +196,6 @@
         mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
         mCallback = new Callback(mMainHandler);
 
-
         mCarrierGroups[0] = view.getCarrier1View();
         mCarrierGroups[1] = view.getCarrier2View();
         mCarrierGroups[2] = view.getCarrier3View();
@@ -149,7 +204,13 @@
         mCarrierDividers[1] = view.getCarrierDivider2();
 
         for (int i = 0; i < SIM_SLOTS; i++) {
-            mInfos[i] = new CellSignalState();
+            mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms,
+                    context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
+                    "", false);
+            mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
+            mLastSignalLevelDescription[i] =
+                    context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
+                            .toString();
             mCarrierGroups[i].setOnClickListener(onClickListener);
         }
         view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -185,10 +246,10 @@
             if (mNetworkController.hasVoiceCallingFeature()) {
                 mNetworkController.addCallback(mSignalCallback);
             }
-            mCarrierTextController.setListening(mCallback);
+            mCarrierTextManager.setListening(mCallback);
         } else {
             mNetworkController.removeCallback(mSignalCallback);
-            mCarrierTextController.setListening(null);
+            mCarrierTextManager.setListening(null);
         }
     }
 
@@ -215,7 +276,7 @@
     }
 
     @MainThread
-    private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+    private void handleUpdateCarrierInfo(CarrierTextManager.CarrierTextCallbackInfo info) {
         if (!mMainHandler.getLooper().isCurrentThread()) {
             mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
             return;
@@ -269,13 +330,13 @@
     }
 
     private static class H extends Handler {
-        private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+        private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
         private Runnable mUpdateState;
         static final int MSG_UPDATE_CARRIER_INFO = 0;
         static final int MSG_UPDATE_STATE = 1;
 
         H(Looper looper,
-                Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+                Consumer<CarrierTextManager.CarrierTextCallbackInfo> updateCarrierInfo,
                 Runnable updateState) {
             super(looper);
             mUpdateCarrierInfo = updateCarrierInfo;
@@ -287,7 +348,7 @@
             switch (msg.what) {
                 case MSG_UPDATE_CARRIER_INFO:
                     mUpdateCarrierInfo.accept(
-                            (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+                            (CarrierTextManager.CarrierTextCallbackInfo) msg.obj);
                     break;
                 case MSG_UPDATE_STATE:
                     mUpdateState.run();
@@ -304,17 +365,19 @@
         private final Handler mHandler;
         private final Looper mLooper;
         private final NetworkController mNetworkController;
-        private final CarrierTextController.Builder mCarrierTextControllerBuilder;
+        private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
+        private final Context mContext;
 
         @Inject
         public Builder(ActivityStarter activityStarter, @Background Handler handler,
                 @Main Looper looper, NetworkController networkController,
-                CarrierTextController.Builder carrierTextControllerBuilder) {
+                CarrierTextManager.Builder carrierTextControllerBuilder, Context context) {
             mActivityStarter = activityStarter;
             mHandler = handler;
             mLooper = looper;
             mNetworkController = networkController;
             mCarrierTextControllerBuilder = carrierTextControllerBuilder;
+            mContext = context;
         }
 
         public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -324,7 +387,7 @@
 
         public QSCarrierGroupController build() {
             return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
-                    mNetworkController, mCarrierTextControllerBuilder);
+                    mNetworkController, mCarrierTextControllerBuilder, mContext);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index abf230e..d4bab21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -102,6 +102,12 @@
         public void onConfigChanged(Configuration newConfig) {
             mView.updateNavBackDrop(newConfig, mLightBarController);
             mView.updateResources();
+            if (mTileAdapter.updateNumColumns()) {
+                RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager();
+                if (lm instanceof GridLayoutManager) {
+                    ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns());
+                }
+            }
         }
     };
 
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 21464fd..048fdc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -98,7 +98,7 @@
     private final UiEventLogger mUiEventLogger;
     private final AccessibilityDelegateCompat mAccessibilityDelegate;
     private RecyclerView mRecyclerView;
-    private final int mNumColumns;
+    private int mNumColumns;
 
     @Inject
     public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
@@ -123,6 +123,21 @@
         mRecyclerView = null;
     }
 
+    /**
+     * Update the number of columns to show, from resources.
+     *
+     * @return {@code true} if the number of columns changed, {@code false} otherwise
+     */
+    public boolean updateNumColumns() {
+        int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns);
+        if (numColumns != mNumColumns) {
+            mNumColumns = numColumns;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public int getNumColumns() {
         return mNumColumns;
     }
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 11e6330..6983b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -37,6 +37,7 @@
 import com.android.systemui.qs.tiles.CellularTile;
 import com.android.systemui.qs.tiles.ColorInversionTile;
 import com.android.systemui.qs.tiles.DataSaverTile;
+import com.android.systemui.qs.tiles.DeviceControlsTile;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.FlashlightTile;
 import com.android.systemui.qs.tiles.HotspotTile;
@@ -89,6 +90,7 @@
     private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
     private final Provider<CameraToggleTile> mCameraToggleTileProvider;
     private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
+    private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
 
     private final Lazy<QSHost> mQsHostLazy;
     private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -123,7 +125,8 @@
             Provider<ScreenRecordTile> screenRecordTileProvider,
             Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
             Provider<CameraToggleTile> cameraToggleTileProvider,
-            Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
+            Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
+            Provider<DeviceControlsTile> deviceControlsTileProvider) {
         mQsHostLazy = qsHostLazy;
         mCustomTileBuilderProvider = customTileBuilderProvider;
 
@@ -153,6 +156,7 @@
         mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
         mCameraToggleTileProvider = cameraToggleTileProvider;
         mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
+        mDeviceControlsTileProvider = deviceControlsTileProvider;
     }
 
     public QSTile createTile(String tileSpec) {
@@ -212,6 +216,8 @@
                 return mCameraToggleTileProvider.get();
             case "mictoggle":
                 return mMicrophoneToggleTileProvider.get();
+            case "controls":
+                return mDeviceControlsTileProvider.get();
         }
 
         // Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
new file mode 100644
index 0000000..4144591
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.util.settings.GlobalSettings
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Provider
+
+class DeviceControlsTile @Inject constructor(
+    host: QSHost,
+    @Background backgroundLooper: Looper,
+    @Main mainHandler: Handler,
+    metricsLogger: MetricsLogger,
+    statusBarStateController: StatusBarStateController,
+    activityStarter: ActivityStarter,
+    qsLogger: QSLogger,
+    private val controlsComponent: ControlsComponent,
+    private val featureFlags: FeatureFlags,
+    private val dialogProvider: Provider<ControlsDialog>,
+    globalSettings: GlobalSettings
+) : QSTileImpl<QSTile.State>(
+        host,
+        backgroundLooper,
+        mainHandler,
+        metricsLogger,
+        statusBarStateController,
+        activityStarter,
+        qsLogger
+) {
+
+    companion object {
+        const val SETTINGS_FLAG = "controls_lockscreen"
+    }
+
+    private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
+    private var hasControlsApps = AtomicBoolean(false)
+    private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+
+    private var controlsDialog: ControlsDialog? = null
+    private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+
+    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+            if (hasControlsApps.compareAndSet(serviceInfos.isEmpty(), serviceInfos.isNotEmpty())) {
+                refreshState()
+            }
+        }
+    }
+
+    init {
+        controlsComponent.getControlsListingController().ifPresent {
+            it.observe(this, listingCallback)
+        }
+    }
+
+    override fun isAvailable(): Boolean {
+        return featureFlags.isKeyguardLayoutEnabled &&
+                controlsLockscreen &&
+                controlsComponent.getVisibility() != UNAVAILABLE
+    }
+
+    override fun newTileState(): QSTile.State {
+        return QSTile.State().also {
+            it.state = Tile.STATE_UNAVAILABLE // Start unavailable matching `hasControlsApps`
+        }
+    }
+
+    override fun handleDestroy() {
+        dismissDialog()
+        super.handleDestroy()
+    }
+
+    private fun createDialog() {
+        if (controlsDialog?.isShowing != true) {
+            controlsDialog = dialogProvider.get()
+        }
+    }
+
+    private fun dismissDialog() {
+        controlsDialog?.dismiss()?.also {
+            controlsDialog = null
+        }
+    }
+
+    override fun handleClick() {
+        if (state.state != Tile.STATE_UNAVAILABLE) {
+            mUiHandler.post {
+                createDialog()
+                controlsDialog?.show(controlsComponent.getControlsUiController().get())
+            }
+        }
+    }
+
+    override fun handleUpdateState(state: QSTile.State, arg: Any?) {
+        state.label = tileLabel
+        state.secondaryLabel = ""
+        state.stateDescription = ""
+        state.contentDescription = state.label
+        state.icon = icon
+        if (hasControlsApps.get()) {
+            state.state = Tile.STATE_ACTIVE
+            if (controlsDialog == null) {
+                mUiHandler.post(this::createDialog)
+            }
+        } else {
+            state.state = Tile.STATE_UNAVAILABLE
+            dismissDialog()
+        }
+    }
+
+    override fun getMetricsCategory(): Int {
+        return 0
+    }
+
+    override fun getLongClickIntent(): Intent {
+        return intent
+    }
+
+    override fun getTileLabel(): CharSequence {
+        return mContext.getText(R.string.quick_controls_title)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 191b85b..0abff77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -469,9 +469,8 @@
             if (wifiConnected) {
                 minimalStateDescription.append(cb.mWifiSignalContentDescription);
                 minimalContentDescription.append(removeDoubleQuotes(cb.mSsid));
-                if (!TextUtils.isEmpty(state.secondaryLabel)) {
-                    minimalContentDescription.append(",").append(state.secondaryLabel);
-                }
+            } else if (!TextUtils.isEmpty(state.secondaryLabel)) {
+                minimalContentDescription.append(",").append(state.secondaryLabel);
             }
         }
         state.stateDescription = minimalStateDescription.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
index 6a8c6149..6ca550c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java
@@ -41,7 +41,7 @@
     protected static int layoutResId = R.layout.qs_user_detail_item;
 
     private UserAvatarView mAvatar;
-    private TextView mName;
+    protected TextView mName;
     private int mActivatedStyle;
     private int mRegularStyle;
     private View mRestrictedPadlock;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index 26adfdc..a6cddd3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -82,7 +82,7 @@
 
     @Override
     public DetailAdapter getDetailAdapter() {
-        return mUserSwitcherController.userDetailAdapter;
+        return mUserSwitcherController.mUserDetailAdapter;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 01a8c1c..5b2a7e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -620,6 +620,19 @@
         }
 
         @Override
+        public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+            if (!verifyCaller("exitSplitScreenOnHide")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void startTask(int taskId, int stage, int position, Bundle options) {
             if (!verifyCaller("startTask")) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 5438743..26781f4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -277,12 +277,12 @@
      */
     void end() {
         mMediaRecorder.stop();
-        mMediaProjection.stop();
         mMediaRecorder.release();
-        mMediaRecorder = null;
-        mMediaProjection = null;
         mInputSurface.release();
         mVirtualDisplay.release();
+        mMediaProjection.stop();
+        mMediaRecorder = null;
+        mMediaProjection = null;
         stopInternalAudioRecording();
 
         Log.d(TAG, "end recording");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index c8afd0b..9383aef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -16,17 +16,22 @@
 
 package com.android.systemui.screenshot;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
 
 import com.android.systemui.R;
 
@@ -35,6 +40,7 @@
  * cropped out.
  */
 public class CropView extends View {
+    private static final String TAG = "CropView";
     public enum CropBoundary {
         NONE, TOP, BOTTOM
     }
@@ -118,10 +124,7 @@
             case MotionEvent.ACTION_UP:
                 if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     // Commit the delta to the stored crop values.
-                    mTopCrop += mTopDelta;
-                    mBottomCrop += mBottomDelta;
-                    mTopDelta = 0;
-                    mBottomDelta = 0;
+                    commitDeltas();
                     updateListener(event);
                 }
         }
@@ -129,6 +132,42 @@
     }
 
     /**
+     * Animate the given boundary to the given value.
+     */
+    public void animateBoundaryTo(CropBoundary boundary, float value) {
+        if (boundary == CropBoundary.NONE) {
+            Log.w(TAG, "No boundary selected for animation");
+            return;
+        }
+        float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop)
+                : (value - mBottomCrop);
+        ValueAnimator animator = new ValueAnimator();
+        animator.addUpdateListener(animation -> {
+            if (boundary == CropBoundary.TOP) {
+                mTopDelta = animation.getAnimatedFraction() * totalDelta;
+            } else {
+                mBottomDelta = animation.getAnimatedFraction() * totalDelta;
+            }
+            invalidate();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                commitDeltas();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                commitDeltas();
+            }
+        });
+        animator.setFloatValues(0f, 1f);
+        animator.setDuration(750);
+        animator.setInterpolator(new FastOutSlowInInterpolator());
+        animator.start();
+    }
+
+    /**
      * @return value [0,1] representing the position of the top crop boundary. Does not reflect
      * changes from any in-progress touch input.
      */
@@ -148,6 +187,13 @@
         mCropInteractionListener = listener;
     }
 
+    private void commitDeltas() {
+        mTopCrop += mTopDelta;
+        mBottomCrop += mBottomDelta;
+        mTopDelta = 0;
+        mBottomDelta = 0;
+    }
+
     private void updateListener(MotionEvent event) {
         if (mCropInteractionListener != null) {
             float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
index 212e6c8..a95c91b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -70,7 +70,7 @@
 
         RecordingCanvas canvas = mNode.beginRecording(w, h);
         canvas.save();
-        canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
+        canvas.clipRect(0, 0, mLocation.width(), mLocation.height());
         canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
                 0, 0, null);
         canvas.restore();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 20f8451..ae3cd99 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -21,6 +21,8 @@
 import android.graphics.Rect;
 import android.graphics.RenderNode;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.Log;
 
 import androidx.annotation.UiThread;
 
@@ -32,11 +34,14 @@
  * <p>
  * To display on-screen, use {@link #getDrawable()}.
  */
-@UiThread
 class ImageTileSet {
 
     private static final String TAG = "ImageTileSet";
 
+    ImageTileSet(@UiThread Handler handler) {
+        mHandler = handler;
+    }
+
     interface OnBoundsChangedListener {
         /**
          * Reports an update to the bounding box that contains all active tiles. These are virtual
@@ -54,6 +59,7 @@
 
     private final List<ImageTile> mTiles = new ArrayList<>();
     private final Rect mBounds = new Rect();
+    private final Handler mHandler;
 
     private OnContentChangedListener mOnContentChangedListener;
     private OnBoundsChangedListener mOnBoundsChangedListener;
@@ -73,13 +79,32 @@
         newBounds.union(newRect);
         if (!newBounds.equals(mBounds)) {
             mBounds.set(newBounds);
-            if (mOnBoundsChangedListener != null) {
-                mOnBoundsChangedListener.onBoundsChanged(
-                        newBounds.left, newBounds.top, newBounds.right, newBounds.bottom);
-            }
+            notifyBoundsChanged(mBounds);
         }
-        if (mOnContentChangedListener != null) {
+        notifyContentChanged();
+    }
+
+    void notifyContentChanged() {
+        if (mOnContentChangedListener == null) {
+            return;
+        }
+        if (mHandler.getLooper().isCurrentThread()) {
             mOnContentChangedListener.onContentChanged();
+        } else {
+            mHandler.post(() -> mOnContentChangedListener.onContentChanged());
+        }
+    }
+
+    void notifyBoundsChanged(Rect bounds) {
+        if (mOnBoundsChangedListener == null) {
+            return;
+        }
+        if (mHandler.getLooper().isCurrentThread()) {
+            mOnBoundsChangedListener.onBoundsChanged(
+                    bounds.left, bounds.top, bounds.right, bounds.bottom);
+        } else {
+            mHandler.post(() -> mOnBoundsChangedListener.onBoundsChanged(
+                    bounds.left, bounds.top, bounds.right, bounds.bottom));
         }
     }
 
@@ -117,22 +142,16 @@
      *               getHeight()).
      */
     Bitmap toBitmap(Rect bounds) {
+        Log.d(TAG, "exporting with bounds: " + bounds);
         if (mTiles.isEmpty()) {
             return null;
         }
         final RenderNode output = new RenderNode("Bitmap Export");
-        output.setPosition(0, 0, getWidth(), getHeight());
+        output.setPosition(0, 0, bounds.width(), bounds.height());
         RecordingCanvas canvas = output.beginRecording();
-        canvas.translate(-getLeft(), -getTop());
-        // Additional translation to account for the requested bounds
-        canvas.translate(-bounds.left, -bounds.top);
-        canvas.clipRect(bounds);
-        for (ImageTile tile : mTiles) {
-            canvas.save();
-            canvas.translate(tile.getLeft(), tile.getTop());
-            canvas.drawRenderNode(tile.getDisplayList());
-            canvas.restore();
-        }
+        Drawable drawable = getDrawable();
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
         output.endRecording();
         return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
     }
@@ -162,14 +181,13 @@
     }
 
     void clear() {
-        mBounds.set(0, 0, 0, 0);
+        if (mBounds.isEmpty()) {
+            return;
+        }
+        mBounds.setEmpty();
         mTiles.forEach(ImageTile::close);
         mTiles.clear();
-        if (mOnBoundsChangedListener != null) {
-            mOnBoundsChangedListener.onBoundsChanged(0, 0, 0, 0);
-        }
-        if (mOnContentChangedListener != null) {
-            mOnContentChangedListener.onContentChanged();
-        }
+        notifyBoundsChanged(mBounds);
+        notifyContentChanged();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index f887151..f8f1d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -144,7 +144,9 @@
                 setAlpha(0f);
                 setTranslationX((getParentWidth() - getWidth()) / 2);
                 setVisibility(View.VISIBLE);
-                animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start();
+                boolean touchOnRight = event.getX() > getParentWidth() / 2;
+                float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth();
+                animate().alpha(1f).translationX(translateXTarget).scaleX(1f).scaleY(1f).start();
                 break;
             case MotionEvent.ACTION_MOVE:
                 mLastCropPosition = cropPosition;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index bb07012..dc639dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -50,8 +50,6 @@
 public class ScrollCaptureClient {
     private static final int TILE_SIZE_PX_MAX = 4 * (1024 * 1024);
     private static final int TILES_PER_PAGE = 2; // increase once b/174571735 is addressed
-    private static final int MAX_PAGES = 5;
-    private static final int MAX_IMAGE_COUNT = MAX_PAGES * TILES_PER_PAGE;
 
     @VisibleForTesting
     static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
@@ -66,10 +64,11 @@
         /**
          * Session start should be deferred until UI is active because of resource allocation and
          * potential visible side effects in the target window.
-
+         *
          * @param sessionConsumer listener to receive the session once active
+         * @param maxPages the capture buffer size expressed as a multiple of the content height
          */
-        void start(Consumer<Session> sessionConsumer);
+        void start(Consumer<Session> sessionConsumer, float maxPages);
 
         /**
          * Close the connection.
@@ -96,6 +95,17 @@
             this.requested = request;
             this.captured = captured;
         }
+
+        @Override
+        public String toString() {
+            return "CaptureResult{"
+                    + "requested=" + requested
+                    + " (" + requested.width() + "x" + requested.height() + ")"
+                    + ", captured=" + captured
+                    + " (" + captured.width() + "x" + captured.height() + ")"
+                    + ", image=" + image
+                    + '}';
+        }
     }
 
     /**
@@ -196,6 +206,7 @@
         private int mTileWidth;
         private Rect mRequestRect;
         private boolean mStarted;
+        private int mMaxTiles;
 
         private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
             mConnectionConsumer = connectionConsumer;
@@ -285,12 +296,15 @@
         // ScrollCaptureController.Connection
 
         @Override
-        public void start(Consumer<Session> sessionConsumer) {
+        public void start(Consumer<Session> sessionConsumer, float maxPages) {
             if (DEBUG_SCROLL) {
-                Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ")");
+                Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
+                        + " maxPages=" + maxPages + ")"
+                        + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]");
             }
+            mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
             mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
-                    MAX_IMAGE_COUNT, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+                    mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
             mSessionConsumer = sessionConsumer;
             try {
                 mConnection.startCapture(mReader.getSurface());
@@ -345,7 +359,7 @@
 
         @Override
         public int getMaxTiles() {
-            return MAX_IMAGE_COUNT;
+            return mMaxTiles;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 25438a6..ad5e637 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -34,6 +35,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
 import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
@@ -44,13 +46,23 @@
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * Interaction controller between the UI and ScrollCaptureClient.
  */
 public class ScrollCaptureController implements OnComputeInternalInsetsListener {
     private static final String TAG = "ScrollCaptureController";
+    private static final float MAX_PAGES_DEFAULT = 3f;
+
+    private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
+
+    private static final int UP = -1;
+    private static final int DOWN = 1;
+
+    private int mDirection = DOWN;
+    private boolean mAtBottomEdge;
+    private boolean mAtTopEdge;
+    private Session mSession;
 
     // TODO: Support saving without additional action.
     private enum PendingAction {
@@ -59,7 +71,6 @@
         SAVE
     }
 
-    public static final int MAX_PAGES = 5;
     public static final int MAX_HEIGHT = 12000;
 
     private final Connection mConnection;
@@ -91,7 +102,7 @@
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
         mUiEventLogger = uiEventLogger;
-        mImageTileSet = new ImageTileSet();
+        mImageTileSet = new ImageTileSet(context.getMainThreadHandler());
     }
 
     /**
@@ -129,7 +140,9 @@
         mEdit.setOnClickListener(this::onClicked);
         mShare.setOnClickListener(this::onClicked);
 
-        mConnection.start(this::startCapture);
+        float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
+                SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
+        mConnection.start(this::startCapture, maxPages);
     }
 
 
@@ -232,41 +245,82 @@
         return mWindow.findViewById(res);
     }
 
+
+    private void onCaptureResult(CaptureResult result) {
+        Log.d(TAG, "onCaptureResult: " + result);
+        boolean emptyResult = result.captured.height() == 0;
+        boolean partialResult = !emptyResult
+                && result.captured.height() < result.requested.height();
+        boolean finish = false;
+
+        if (partialResult || emptyResult) {
+            // Potentially reached a vertical boundary. Extend in the other direction.
+            switch (mDirection) {
+                case DOWN:
+                    Log.d(TAG, "Reached bottom edge.");
+                    mAtBottomEdge = true;
+                    mDirection = UP;
+                    break;
+                case UP:
+                    Log.d(TAG, "Reached top edge.");
+                    mAtTopEdge = true;
+                    mDirection = DOWN;
+                    break;
+            }
+
+            if (mAtTopEdge && mAtBottomEdge) {
+                Log.d(TAG, "Reached both top and bottom edge, ending.");
+                finish = true;
+            } else {
+                // only reverse if the edge was relatively close to the starting point
+                if (mImageTileSet.getHeight() < mSession.getPageHeight() * 3) {
+                    Log.d(TAG, "Restarting in reverse direction.");
+
+                    // Because of temporary limitations, we cannot just jump to the opposite edge
+                    // and continue there. Instead, clear the results and start over capturing from
+                    // here in the other direction.
+                    mImageTileSet.clear();
+                } else {
+                    Log.d(TAG, "Capture is tall enough, stopping here.");
+                    finish = true;
+                }
+            }
+        }
+
+        if (!emptyResult) {
+            mImageTileSet.addTile(new ImageTile(result.image, result.captured));
+        }
+
+        Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
+                + " - " +  mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
+                + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+
+
+        // Stop when "too tall"
+        if (mImageTileSet.size() >= mSession.getMaxTiles()
+                || mImageTileSet.getHeight() > MAX_HEIGHT) {
+            Log.d(TAG, "Max height and/or tile count reached.");
+            finish = true;
+        }
+
+        if (finish) {
+            Session session = mSession;
+            mSession = null;
+            Log.d(TAG, "Stop.");
+            mUiExecutor.execute(() -> afterCaptureComplete(session));
+            return;
+        }
+
+        int nextTop = (mDirection == DOWN) ? result.captured.bottom
+                : result.captured.top - mSession.getTileHeight();
+        Log.d(TAG, "requestTile: " + nextTop);
+        mSession.requestTile(nextTop, /* consumer */ this::onCaptureResult);
+    }
+
     private void startCapture(Session session) {
-        Log.d(TAG, "startCapture");
-        Consumer<ScrollCaptureClient.CaptureResult> consumer =
-                new Consumer<ScrollCaptureClient.CaptureResult>() {
-
-                    int mFrameCount = 0;
-                    int mTop = 0;
-
-                    @Override
-                    public void accept(ScrollCaptureClient.CaptureResult result) {
-                        mFrameCount++;
-
-                        boolean emptyFrame = result.captured.height() == 0;
-                        if (!emptyFrame) {
-                            ImageTile tile = new ImageTile(result.image, result.captured);
-                            Log.d(TAG, "Adding tile: " + tile);
-                            mImageTileSet.addTile(tile);
-                            Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", "
-                                    + "h=" + mImageTileSet.getHeight());
-                        }
-
-                        if (emptyFrame || mFrameCount >= MAX_PAGES
-                                || mTop + session.getTileHeight() > MAX_HEIGHT) {
-
-                            mUiExecutor.execute(() -> afterCaptureComplete(session));
-                            return;
-                        }
-                        mTop += result.captured.height();
-                        session.requestTile(mTop, /* consumer */ this);
-                    }
-                };
-
-        // fire it up!
-        session.requestTile(0, consumer);
-    };
+        mSession = session;
+        session.requestTile(0, this::onCaptureResult);
+    }
 
     @UiThread
     void afterCaptureComplete(Session session) {
@@ -277,6 +331,7 @@
         } else {
             mPreview.setImageDrawable(mImageTileSet.getDrawable());
             mMagnifierView.setImageTileset(mImageTileSet);
+            mCropView.animateBoundaryTo(CropView.CropBoundary.BOTTOM, 0.5f);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
index 72f489b..4ec8eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java
@@ -56,7 +56,7 @@
             mNode = new RenderNode("TiledImageDrawable");
         }
         mNode.setPosition(0, 0, mTiles.getWidth(), mTiles.getHeight());
-        Canvas canvas = mNode.beginRecording(mTiles.getWidth(), mTiles.getHeight());
+        Canvas canvas = mNode.beginRecording();
         // Align content (virtual) top/left with 0,0, within the render node
         canvas.translate(-mTiles.getLeft(), -mTiles.getTop());
         for (int i = 0; i < mTiles.size(); i++) {
@@ -79,8 +79,8 @@
         if (canvas.isHardwareAccelerated()) {
             Rect bounds = getBounds();
             canvas.save();
-            canvas.clipRect(bounds);
-            canvas.translate(bounds.left, bounds.top);
+            canvas.clipRect(0, 0, bounds.width(), bounds.height());
+            canvas.translate(-bounds.left, -bounds.top);
             canvas.drawRenderNode(mNode);
             canvas.restore();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 43bb343..0bfc8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -41,7 +41,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.RestrictedLockUtilsInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2dd85e9..862c279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -74,4 +74,12 @@
     public boolean isPeopleTileEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_conversations);
     }
+
+    public boolean isToastStyleEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_toast_style);
+    }
+
+    public boolean isMonetEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_monet);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
index 0465ebf..edcf6d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AssistantFeedbackController.java
@@ -18,23 +18,19 @@
 
 import static android.service.notification.NotificationListenerService.Ranking;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK;
+
 import android.app.NotificationManager;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.util.Pair;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
 
@@ -45,10 +41,10 @@
  * should show an indicator.
  */
 @SysUISingleton
-public class AssistantFeedbackController extends ContentObserver {
-    private final Uri FEEDBACK_URI
-            = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_FEEDBACK_ENABLED);
-    private ContentResolver mResolver;
+public class AssistantFeedbackController {
+    private final Context mContext;
+    private final Handler mHandler;
+    private final DeviceConfigProxy mDeviceConfigProxy;
 
     public static final int STATUS_UNCHANGED = 0;
     public static final int STATUS_ALERTED = 1;
@@ -56,34 +52,39 @@
     public static final int STATUS_PROMOTED = 3;
     public static final int STATUS_DEMOTED = 4;
 
-    private boolean mFeedbackEnabled;
+    private volatile boolean mFeedbackEnabled;
+
+    private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                    if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) {
+                        mFeedbackEnabled = properties.getBoolean(
+                                ENABLE_NAS_FEEDBACK, false);
+                    }
+                }
+            };
 
     /** Injected constructor */
     @Inject
-    public AssistantFeedbackController(Context context) {
-        super(new Handler(Looper.getMainLooper()));
-        mResolver = context.getContentResolver();
-        mResolver.registerContentObserver(FEEDBACK_URI, false, this, UserHandle.USER_ALL);
-        update(null);
+    public AssistantFeedbackController(@Main Handler handler,
+            Context context, DeviceConfigProxy proxy) {
+        mHandler = handler;
+        mContext = context;
+        mDeviceConfigProxy = proxy;
+        mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                ENABLE_NAS_FEEDBACK, false);
+        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                this::postToHandler, mPropertiesChangedListener);
     }
 
-    @Override
-    public void onChange(boolean selfChange, @Nullable Uri uri, int flags) {
-        update(uri);
-    }
-
-    @VisibleForTesting
-    public void update(@Nullable Uri uri) {
-        if (uri == null || FEEDBACK_URI.equals(uri)) {
-            mFeedbackEnabled = Settings.Global.getInt(mResolver,
-                    Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, 0)
-                    != 0;
-        }
+    private void postToHandler(Runnable r) {
+        this.mHandler.post(r);
     }
 
     /**
-     * Determines whether to show any user controls related to the assistant. This is based on the
-     * settings flag {@link Settings.Global.NOTIFICATION_FEEDBACK_ENABLED}
+     * Determines whether to show any user controls related to the assistant based on the
+     * DeviceConfig flag value
      */
     public boolean isFeedbackEnabled() {
         return mFeedbackEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a03fc13..845d321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -71,6 +71,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.widget.CachingIconView;
+import com.android.internal.widget.CallLayout;
 import com.android.internal.widget.MessagingLayout;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
@@ -165,6 +166,7 @@
     private int mMaxSmallHeightLarge;
     private int mMaxSmallHeightMedia;
     private int mMaxExpandedHeight;
+    private int mMaxCallHeight;
     private int mIncreasedPaddingBetweenElements;
     private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
@@ -645,8 +647,9 @@
     }
 
     private void updateLimitsForView(NotificationContentView layout) {
-        boolean customView = layout.getContractedChild() != null
-                && layout.getContractedChild().getId()
+        View contractedView = layout.getContractedChild();
+        boolean customView = contractedView != null
+                && contractedView.getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
         boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
@@ -661,7 +664,8 @@
         View expandedView = layout.getExpandedChild();
         boolean isMediaLayout = expandedView != null
                 && expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
-        boolean isMessagingLayout = layout.getContractedChild() instanceof MessagingLayout;
+        boolean isMessagingLayout = contractedView instanceof MessagingLayout;
+        boolean isCallLayout = contractedView instanceof CallLayout;
         boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
 
         if (customView && beforeS && !mIsSummaryWithChildren) {
@@ -684,6 +688,8 @@
             //  make sure we don't crop them terribly.  We actually need to revisit this and give
             //  them a headerless design, then remove this hack.
             smallHeight = mMaxSmallHeightLarge;
+        } else if (isCallLayout) {
+            smallHeight = mMaxCallHeight;
         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
             smallHeight = mMaxSmallHeightLarge;
         } else {
@@ -1645,6 +1651,8 @@
                 R.dimen.notification_min_height_media);
         mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_height);
+        mMaxCallHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.call_notification_full_height);
         mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
         mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
new file mode 100644
index 0000000..4541ebf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.content.Context
+import android.view.View
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.CallLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/**
+ * Wraps a notification containing a call template
+ */
+class NotificationCallTemplateViewWrapper constructor(
+    ctx: Context,
+    view: View,
+    row: ExpandableNotificationRow
+) : NotificationTemplateViewWrapper(ctx, view, row) {
+
+    private val minHeightWithActions: Int =
+            NotificationUtils.getFontScaledHeight(ctx, R.dimen.call_notification_full_height)
+    private val callLayout: CallLayout = view as CallLayout
+
+    private lateinit var conversationIconView: CachingIconView
+    private lateinit var conversationBadgeBg: View
+    private lateinit var expandBtn: View
+    private lateinit var appName: View
+    private lateinit var conversationTitleView: View
+
+    private fun resolveViews() {
+        with(callLayout) {
+            conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
+            conversationBadgeBg =
+                    requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
+            expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+            appName = requireViewById(com.android.internal.R.id.app_name_text)
+            conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
+        }
+    }
+
+    override fun onContentUpdated(row: ExpandableNotificationRow) {
+        // Reinspect the notification. Before the super call, because the super call also updates
+        // the transformation types and we need to have our values set by then.
+        resolveViews()
+        super.onContentUpdated(row)
+    }
+
+    override fun updateTransformedTypes() {
+        // This also clears the existing types
+        super.updateTransformedTypes()
+        addTransformedViews(
+                appName,
+                conversationTitleView
+        )
+        addViewsTransformingToSimilar(
+                conversationIconView,
+                conversationBadgeBg,
+                expandBtn
+        )
+    }
+
+    override fun disallowSingleClick(x: Float, y: Float): Boolean {
+        val isOnExpandButton = expandBtn.visibility == View.VISIBLE &&
+                isOnView(expandBtn, x, y)
+        return isOnExpandButton || super.disallowSingleClick(x, y)
+    }
+
+    override fun getMinLayoutHeight(): Int = minHeightWithActions
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index c49f6cb..905bccf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.view.View
-import android.view.View.GONE
 import android.view.ViewGroup
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
@@ -48,8 +47,8 @@
 
     private lateinit var conversationIconView: CachingIconView
     private lateinit var conversationBadgeBg: View
-    private lateinit var expandButton: View
-    private lateinit var expandButtonContainer: View
+    private lateinit var expandBtn: View
+    private lateinit var expandBtnContainer: View
     private lateinit var imageMessageContainer: ViewGroup
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
@@ -66,9 +65,8 @@
             conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
             conversationBadgeBg =
                     requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
-            expandButton = requireViewById(com.android.internal.R.id.expand_button)
-            expandButtonContainer =
-                    requireViewById(com.android.internal.R.id.expand_button_container)
+            expandBtn = requireViewById(com.android.internal.R.id.expand_button)
+            expandBtnContainer = requireViewById(com.android.internal.R.id.expand_button_container)
             importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
             appName = requireViewById(com.android.internal.R.id.app_name_text)
             conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
@@ -126,7 +124,7 @@
         addViewsTransformingToSimilar(
                 conversationIconView,
                 conversationBadgeBg,
-                expandButton,
+                expandBtn,
                 importanceRing,
                 facePileTop,
                 facePileBottom,
@@ -134,11 +132,9 @@
         )
     }
 
-    override fun getExpandButton() = super.getExpandButton()
-
     override fun setShelfIconVisible(visible: Boolean) {
         if (conversationLayout.isImportantConversation) {
-            if (conversationIconView.visibility != GONE) {
+            if (conversationIconView.visibility != View.GONE) {
                 conversationIconView.isForceHidden = visible
                 // We don't want the small icon to be hidden by the extended wrapper, as force
                 // hiding the conversationIcon will already do that via its listener.
@@ -152,7 +148,7 @@
 
     override fun getShelfTransformationTarget(): View? =
             if (conversationLayout.isImportantConversation)
-                if (conversationIconView.visibility != GONE)
+                if (conversationIconView.visibility != View.GONE)
                     conversationIconView
                 else
                     // A notification with a fallback icon was set to important. Currently
@@ -169,8 +165,8 @@
             conversationLayout.updateExpandability(expandable, onClickListener)
 
     override fun disallowSingleClick(x: Float, y: Float): Boolean {
-        val isOnExpandButton = expandButtonContainer.visibility == View.VISIBLE &&
-                isOnView(expandButtonContainer, x, y)
+        val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
+                isOnView(expandBtnContainer, x, y)
         return isOnExpandButton || super.disallowSingleClick(x, y)
     }
 
@@ -179,10 +175,4 @@
                 minHeightWithActions
             else
                 super.getMinLayoutHeight()
-
-    private fun addTransformedViews(vararg vs: View?) =
-            vs.forEach { view -> view?.let(mTransformationHelper::addTransformedView) }
-
-    private fun addViewsTransformingToSimilar(vararg vs: View?) =
-            vs.forEach { view -> view?.let(mTransformationHelper::addViewTransformingToSimilar) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 97201f5..34bc537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -36,7 +36,6 @@
 
 import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.NotificationExpandButton;
-import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.ViewTransformationHelper;
@@ -60,6 +59,7 @@
     private CachingIconView mIcon;
     private NotificationExpandButton mExpandButton;
     private View mAltExpandTarget;
+    private View mIconContainer;
     protected NotificationHeaderView mNotificationHeader;
     protected NotificationTopLineView mNotificationTopLine;
     private TextView mHeaderText;
@@ -112,6 +112,7 @@
         mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
         mAltExpandTarget = mView.findViewById(com.android.internal.R.id.alternate_expand_target);
+        mIconContainer = mView.findViewById(com.android.internal.R.id.conversation_icon_container);
         mLeftIcon = mView.findViewById(com.android.internal.R.id.left_icon);
         mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
@@ -203,11 +204,8 @@
     public void clearConversationSkin() {
         if (mAppNameText != null) {
             final ColorStateList colors = mAppNameText.getTextColors();
-            final int textAppearance = Utils.getThemeAttr(
-                    mAppNameText.getContext(),
-                    com.android.internal.R.attr.notificationHeaderTextAppearance,
+            mAppNameText.setTextAppearance(
                     com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
-            mAppNameText.setTextAppearance(textAppearance);
             mAppNameText.setTextColor(colors);
             MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
             final int marginStart = mAppNameText.getResources().getDimensionPixelSize(
@@ -265,19 +263,11 @@
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
         mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_EXPANDER,
                 mExpandButton);
-        if (mWorkProfileImage != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mWorkProfileImage);
-        }
         if (mIsLowPriority && mHeaderText != null) {
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);
         }
-        if (mAudiblyAlertedIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
-        }
-        if (mFeedbackIcon != null) {
-            mTransformationHelper.addViewTransformingToSimilar(mFeedbackIcon);
-        }
+        addViewsTransformingToSimilar(mWorkProfileImage, mAudiblyAlertedIcon, mFeedbackIcon);
     }
 
     @Override
@@ -287,6 +277,9 @@
         if (mAltExpandTarget != null) {
             mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
         }
+        if (mIconContainer != null) {
+            mIconContainer.setOnClickListener(expandable ? onClickListener : null);
+        }
         if (mNotificationHeader != null) {
             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
         }
@@ -371,4 +364,20 @@
         super.setVisible(visible);
         mTransformationHelper.setVisible(visible);
     }
+
+    protected void addTransformedViews(View... views) {
+        for (View view : views) {
+            if (view != null) {
+                mTransformationHelper.addTransformedView(view);
+            }
+        }
+    }
+
+    protected void addViewsTransformingToSimilar(View... views) {
+        for (View view : views) {
+            if (view != null) {
+                mTransformationHelper.addViewTransformingToSimilar(view);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index b3d1a94..5fff8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -74,6 +74,8 @@
                 return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
             } else if ("conversation".equals(v.getTag())) {
                 return new NotificationConversationTemplateViewWrapper(ctx, v, row);
+            } else if ("call".equals(v.getTag())) {
+                return new NotificationCallTemplateViewWrapper(ctx, v, row);
             }
             Class<? extends Notification.Style> style =
                     row.getEntry().getSbn().getNotification().getNotificationStyle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 2ce0a87..986333c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -183,6 +184,7 @@
     private ControlsComponent mControlsComponent;
     private int mLockScreenMode;
     private BroadcastDispatcher mBroadcastDispatcher;
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
@@ -295,7 +297,8 @@
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
                 UserHandle.ALL, filter, null, null);
-        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
         mKeyguardStateController.addCallback(this);
     }
 
@@ -307,7 +310,7 @@
         mRightExtension.destroy();
         mLeftExtension.destroy();
         getContext().unregisterReceiver(mDevicePolicyReceiver);
-        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
     }
 
     private void initAccessibility() {
@@ -410,12 +413,6 @@
     }
 
     private void updateLeftAffordanceIcon() {
-        if (mDozing) {
-            mAltLeftButton.setVisibility(GONE);
-        } else if (mAltLeftButton.getDrawable() != null) {
-            mAltLeftButton.setVisibility(VISIBLE);
-        }
-
         if (!mShowLeftAffordance || mDozing) {
             mLeftAffordanceView.setVisibility(GONE);
             return;
@@ -430,6 +427,14 @@
         mLeftAffordanceView.setContentDescription(state.contentDescription);
     }
 
+    private void updateControlsVisibility() {
+        if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) {
+            mAltLeftButton.setVisibility(GONE);
+        } else {
+            mAltLeftButton.setVisibility(VISIBLE);
+        }
+    }
+
     public boolean isLeftVoiceAssist() {
         return mLeftIsVoiceAssist;
     }
@@ -769,6 +774,7 @@
 
         updateCameraVisibility();
         updateLeftAffordanceIcon();
+        updateControlsVisibility();
 
         if (dozing) {
             mOverlayContainer.setVisibility(INVISIBLE);
@@ -889,35 +895,27 @@
     }
 
     private void setupControls() {
-        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+        boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+        boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                "controls_lockscreen", 0) == 1;
+        if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) {
             mAltLeftButton.setVisibility(View.GONE);
-            mAltLeftButton.setOnClickListener(null);
             return;
         }
 
-        if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) {
-            return;
-        }
-
-        if (mControlsComponent.getControlsListingController().isPresent()) {
-            mControlsComponent.getControlsListingController().get()
-                    .addCallback(list -> {
-                        if (!list.isEmpty()) {
-                            mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
-                            mAltLeftButton.setVisibility(View.VISIBLE);
-                            mAltLeftButton.setOnClickListener((v) -> {
-                                ControlsUiController ui = mControlsComponent
-                                        .getControlsUiController().get();
-                                mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
-                                        .show(ui);
-                            });
-
-                        } else {
-                            mAltLeftButton.setVisibility(View.GONE);
-                            mAltLeftButton.setOnClickListener(null);
-                        }
-                    });
-        }
+        mControlsComponent.getControlsListingController().get()
+                .addCallback(list -> {
+                    if (!list.isEmpty()) {
+                        mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
+                        mAltLeftButton.setOnClickListener((v) -> {
+                            ControlsUiController ui = mControlsComponent
+                                    .getControlsUiController().get();
+                            mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
+                                    .show(ui);
+                        });
+                    }
+                    updateControlsVisibility();
+                });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 57a64e4..a6daed5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
 
 /**
  * Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -55,11 +56,24 @@
     private int mKeyguardStatusHeight;
 
     /**
+     * Height of user avatar used by the multi-user switcher. This could either be the
+     * {@link KeyguardUserSwitcherListView} when it is closed and only the current user's icon is
+     * visible, or it could be height of the avatar used by the
+     * {@link com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController}.
+     */
+    private int mUserSwitchHeight;
+
+    /**
      * Preferred Y position of clock.
      */
     private int mClockPreferredY;
 
     /**
+     * Preferred Y position of user avatar used by the multi-user switcher.
+     */
+    private int mUserSwitchPreferredY;
+
+    /**
      * Whether or not there is a custom clock face on keyguard.
      */
     private boolean mHasCustomClock;
@@ -173,18 +187,22 @@
      * Sets up algorithm values.
      */
     public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
-            float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
+            float panelExpansion, int parentHeight, int keyguardStatusHeight,
+            int userSwitchHeight, int clockPreferredY, int userSwitchPreferredY,
             boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
             boolean bypassEnabled, int unlockedStackScrollerPadding, boolean showLockIcon,
             float qsExpansion, int cutoutTopInset) {
         mMinTopMargin = statusBarMinHeight + (showLockIcon
-                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon);
+                ? mContainerTopPaddingWithLockIcon : mContainerTopPaddingWithoutLockIcon)
+                + userSwitchHeight;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
         mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
+        mUserSwitchHeight = userSwitchHeight;
         mClockPreferredY = clockPreferredY;
+        mUserSwitchPreferredY = userSwitchPreferredY;
         mHasCustomClock = hasCustomClock;
         mHasVisibleNotifs = hasVisibleNotifs;
         mDarkAmount = dark;
@@ -198,6 +216,7 @@
     public void run(Result result) {
         final int y = getClockY(mPanelExpansion, mDarkAmount);
         result.clockY = y;
+        result.userSwitchY = getUserSwitcherY(mPanelExpansion);
         result.clockYFullyDozing = getClockY(
                 1.0f /* panelExpansion */, 1.0f /* darkAmount */);
         result.clockAlpha = getClockAlpha(y);
@@ -231,7 +250,7 @@
 
     private int getExpandedPreferredClockY() {
         if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
-            return mMinTopMargin;
+            return mMinTopMargin + mUserSwitchHeight;
         }
         return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
                 : getExpandedClockPosition();
@@ -246,7 +265,8 @@
         final int availableHeight = mMaxShadeBottom - mMinTopMargin;
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
-        float y = containerCenter - mKeyguardStatusHeight * CLOCK_HEIGHT_WEIGHT
+        float y = containerCenter
+                - (mKeyguardStatusHeight + mUserSwitchHeight) * CLOCK_HEIGHT_WEIGHT
                 - mClockNotificationsMargin - mNotificationStackHeight / 2;
         if (y < mMinTopMargin) {
             y = mMinTopMargin;
@@ -288,6 +308,17 @@
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
     }
 
+    private int getUserSwitcherY(float panelExpansion) {
+        float userSwitchYRegular = mUserSwitchPreferredY;
+        float userSwitchYBouncer = -mKeyguardStatusHeight - mUserSwitchHeight;
+
+        // Move user-switch up while collapsing the shade
+        float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
+        float userSwitchY = MathUtils.lerp(userSwitchYBouncer, userSwitchYRegular, shadeExpansion);
+
+        return (int) (userSwitchY + mEmptyDragAmount);
+    }
+
     /**
      * We might want to fade out the clock when the user is swiping up.
      * One exception is when the bouncer will become visible, in this cause the clock
@@ -330,6 +361,11 @@
         public int clockY;
 
         /**
+         * The y translation of the multi-user switch.
+         */
+        public int userSwitchY;
+
+        /**
          * The y translation of the clock when we're fully dozing.
          */
         public int clockYFullyDozing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 5f547b5..33798d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
 
 import android.annotation.ColorInt;
@@ -25,6 +26,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -45,18 +47,15 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,18 +74,16 @@
 
     private boolean mShowPercentAvailable;
     private boolean mBatteryCharging;
-    private boolean mKeyguardUserSwitcherShowing;
     private boolean mBatteryListening;
 
     private TextView mCarrierLabel;
-    private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
     private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
 
     private BatteryController mBatteryController;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private UserSwitcherController mUserSwitcherController;
+    private boolean mKeyguardUserSwitcherEnabled;
+    private final UserManager mUserManager;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
     private int mSystemIconsBaseMargin;
@@ -109,13 +106,13 @@
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mUserManager = UserManager.get(getContext());
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
-        mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -124,7 +121,6 @@
         mStatusIconContainer = findViewById(R.id.statusIcons);
 
         loadDimens();
-        updateUserSwitcher();
         mBatteryController = Dependency.get(BatteryController.class);
     }
 
@@ -137,14 +133,6 @@
                 R.dimen.multi_user_avatar_keyguard_size);
         mMultiUserAvatar.setLayoutParams(lp);
 
-        // Multi-user switch
-        lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams();
-        lp.width = getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_width_keyguard);
-        lp.setMarginEnd(getResources().getDimensionPixelSize(
-                R.dimen.multi_user_switch_keyguard_margin));
-        mMultiUserSwitch.setLayoutParams(lp);
-
         // System icons
         lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
         lp.setMarginStart(getResources().getDimensionPixelSize(
@@ -194,22 +182,28 @@
     }
 
     private void updateVisibilities() {
-        if (mMultiUserSwitch.getParent() != mStatusIconArea && !mKeyguardUserSwitcherShowing) {
-            if (mMultiUserSwitch.getParent() != null) {
-                getOverlay().remove(mMultiUserSwitch);
+        if (mMultiUserAvatar.getParent() != mStatusIconArea
+                && !mKeyguardUserSwitcherEnabled) {
+            if (mMultiUserAvatar.getParent() != null) {
+                getOverlay().remove(mMultiUserAvatar);
             }
-            mStatusIconArea.addView(mMultiUserSwitch, 0);
-        } else if (mMultiUserSwitch.getParent() == mStatusIconArea && mKeyguardUserSwitcherShowing) {
-            mStatusIconArea.removeView(mMultiUserSwitch);
+            mStatusIconArea.addView(mMultiUserAvatar, 0);
+        } else if (mMultiUserAvatar.getParent() == mStatusIconArea
+                && mKeyguardUserSwitcherEnabled) {
+            mStatusIconArea.removeView(mMultiUserAvatar);
         }
-        if (mKeyguardUserSwitcher == null) {
+        if (!mKeyguardUserSwitcherEnabled) {
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
             // we only show the multi-user switch if it's enabled through UserManager as well as
             // by the user.
-            if (mMultiUserSwitch.isMultiUserEnabled()) {
-                mMultiUserSwitch.setVisibility(View.VISIBLE);
+            // TODO(b/138661450) Move IPC calls to background
+            boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
+                    mContext.getResources().getBoolean(
+                            R.bool.qs_show_user_switcher_for_single_user)));
+            if (isMultiUserEnabled) {
+                mMultiUserAvatar.setVisibility(View.VISIBLE);
             } else {
-                mMultiUserSwitch.setVisibility(View.GONE);
+                mMultiUserAvatar.setVisibility(View.GONE);
             }
         }
         mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
@@ -220,11 +214,12 @@
                 (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
         // If the avatar icon is gone, we need to have some end margin to display the system icons
         // correctly.
-        int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+        int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
                 ? mSystemIconsBaseMargin
                 : 0;
-        int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
-                baseMarginEnd;
+        int marginEnd =
+                mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
+                        : baseMarginEnd;
         marginEnd = calculateMargin(marginEnd, mPadding.second);
         if (marginEnd != lp.getMarginEnd()) {
             lp.setMarginEnd(marginEnd);
@@ -334,20 +329,11 @@
         }
     }
 
-    private void updateUserSwitcher() {
-        boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
-        mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setFocusable(keyguardSwitcherAvailable);
-        mMultiUserSwitch.setKeyguardMode(keyguardSwitcherAvailable);
-    }
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
         userInfoController.addCallback(this);
-        mUserSwitcherController = Dependency.get(UserSwitcherController.class);
-        mMultiUserSwitch.setUserSwitcherController(mUserSwitcherController);
         userInfoController.reloadUserInfo();
         Dependency.get(ConfigurationController.class).addCallback(this);
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons),
@@ -369,11 +355,6 @@
         mMultiUserAvatar.setImageDrawable(picture);
     }
 
-    /** */
-    public void setQSDetailDisplayer(QSDetailDisplayer detailDisplayer) {
-        mMultiUserSwitch.setQSDetailDisplayer(detailDisplayer);
-    }
-
     @Override
     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         if (mBatteryCharging != charging) {
@@ -387,54 +368,42 @@
         // could not care less
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-        mMultiUserSwitch.setKeyguardUserSwitcher(keyguardUserSwitcher);
-        updateUserSwitcher();
-    }
-
-    public void setKeyguardUserSwitcherShowing(boolean showing, boolean animate) {
-        mKeyguardUserSwitcherShowing = showing;
-        if (animate) {
-            animateNextLayoutChange();
-        }
-        updateVisibilities();
-        updateLayoutConsideringCutout();
-        updateSystemIconsLayoutParams();
+    public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+        mKeyguardUserSwitcherEnabled = enabled;
     }
 
     private void animateNextLayoutChange() {
         final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
-        final boolean userSwitcherVisible = mMultiUserSwitch.getParent() == mStatusIconArea;
+        final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
                 getViewTreeObserver().removeOnPreDrawListener(this);
-                boolean userSwitcherHiding = userSwitcherVisible
-                        && mMultiUserSwitch.getParent() != mStatusIconArea;
+                boolean userAvatarHiding = userAvatarVisible
+                        && mMultiUserAvatar.getParent() != mStatusIconArea;
                 mSystemIconsContainer.setX(systemIconsCurrentX);
                 mSystemIconsContainer.animate()
                         .translationX(0)
                         .setDuration(400)
-                        .setStartDelay(userSwitcherHiding ? 300 : 0)
+                        .setStartDelay(userAvatarHiding ? 300 : 0)
                         .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                         .start();
-                if (userSwitcherHiding) {
-                    getOverlay().add(mMultiUserSwitch);
-                    mMultiUserSwitch.animate()
+                if (userAvatarHiding) {
+                    getOverlay().add(mMultiUserAvatar);
+                    mMultiUserAvatar.animate()
                             .alpha(0f)
                             .setDuration(300)
                             .setStartDelay(0)
                             .setInterpolator(Interpolators.ALPHA_OUT)
                             .withEndAction(() -> {
-                                mMultiUserSwitch.setAlpha(1f);
-                                getOverlay().remove(mMultiUserSwitch);
+                                mMultiUserAvatar.setAlpha(1f);
+                                getOverlay().remove(mMultiUserAvatar);
                             })
                             .start();
 
                 } else {
-                    mMultiUserSwitch.setAlpha(0f);
-                    mMultiUserSwitch.animate()
+                    mMultiUserAvatar.setAlpha(0f);
+                    mMultiUserAvatar.animate()
                             .alpha(1f)
                             .setDuration(300)
                             .setStartDelay(200)
@@ -452,8 +421,8 @@
         if (visibility != View.VISIBLE) {
             mSystemIconsContainer.animate().cancel();
             mSystemIconsContainer.setTranslationX(0);
-            mMultiUserSwitch.animate().cancel();
-            mMultiUserSwitch.setAlpha(1f);
+            mMultiUserAvatar.animate().cancel();
+            mMultiUserAvatar.setAlpha(1f);
         } else {
             updateVisibilities();
             updateSystemIconsLayoutParams();
@@ -523,9 +492,9 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryCharging: " + mBatteryCharging);
-        pw.println("  mKeyguardUserSwitcherShowing: " + mKeyguardUserSwitcherShowing);
         pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mLayoutState: " + mLayoutState);
+        pw.println("  mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
         if (mBatteryView != null) {
             mBatteryView.dump(fd, pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
new file mode 100644
index 0000000..377fb92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.keyguard.CarrierTextController;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
+public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+    private final CarrierTextController mCarrierTextController;
+
+    @Inject
+    public KeyguardStatusBarViewController(
+            KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+        super(view);
+        mCarrierTextController = carrierTextController;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mCarrierTextController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 480d3f4..16f36b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -35,7 +35,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSDetailDisplayer;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 /**
@@ -44,8 +43,6 @@
 public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
 
     protected QSDetailDisplayer mQSDetailDisplayer;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private boolean mKeyguardMode;
     private UserSwitcherController.BaseUserAdapter mUserListener;
 
     final UserManager mUserManager;
@@ -85,15 +82,6 @@
         refreshContentDescription();
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
-    public void setKeyguardMode(boolean keyguardShowing) {
-        mKeyguardMode = keyguardShowing;
-        registerListener();
-    }
-
     public boolean isMultiUserEnabled() {
         // TODO(b/138661450) Move IPC calls to background
         return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
@@ -123,11 +111,7 @@
 
     @Override
     public void onClick(View v) {
-        if (mKeyguardMode) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.show(true /* animate */);
-            }
-        } else if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
+        if (mQSDetailDisplayer != null && mUserSwitcherController != null) {
             View center = getChildCount() > 0 ? getChildAt(0) : this;
 
             int[] tmpInt = new int[2];
@@ -184,6 +168,6 @@
     }
 
     protected DetailAdapter getUserDetailAdapter() {
-        return mUserSwitcherController.userDetailAdapter;
+        return mUserSwitcherController.mUserDetailAdapter;
     }
 }
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 e0ef3b6..8aadef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,10 @@
 
 import static android.view.View.GONE;
 
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+import static androidx.constraintlayout.widget.ConstraintSet.START;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -48,6 +52,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.UserManager;
 import android.util.Log;
 import android.util.MathUtils;
 import android.view.DisplayCutout;
@@ -57,6 +62,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityManager;
@@ -64,6 +70,8 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintSet;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
@@ -75,7 +83,10 @@
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -92,6 +103,7 @@
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -131,9 +143,12 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.Utils;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import java.io.FileDescriptor;
@@ -283,6 +298,21 @@
                 }
     };
 
+    final KeyguardUserSwitcherController.KeyguardUserSwitcherListener
+            mKeyguardUserSwitcherListener =
+            new KeyguardUserSwitcherController.KeyguardUserSwitcherListener() {
+                @Override
+                public void onKeyguardUserSwitcherChanged(boolean open) {
+                    if (mKeyguardUserSwitcherController == null) {
+                        updateUserSwitcherVisibility(false);
+                    } else if (!mKeyguardUserSwitcherController.isSimpleUserSwitcher()) {
+                        updateUserSwitcherVisibility(open
+                                && mKeyguardStateController.isShowing()
+                                && !mKeyguardStateController.isKeyguardFadingAway());
+                    }
+                }
+            };
+
     private final LayoutInflater mLayoutInflater;
     private final PowerManager mPowerManager;
     private final AccessibilityManager mAccessibilityManager;
@@ -295,6 +325,9 @@
     private final MediaHierarchyManager mMediaHierarchyManager;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
+    private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+    private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     private final QSDetailDisplayer mQSDetailDisplayer;
     private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
@@ -307,8 +340,11 @@
     private int mMaxAllowedKeyguardNotifications;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
+    private boolean mKeyguardUserSwitcherIsShowing;
+    private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
+    private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -332,6 +368,8 @@
     private boolean mQsExpandedWhenExpandingStarted;
     private boolean mQsFullyExpanded;
     private boolean mKeyguardShowing;
+    private boolean mKeyguardQsUserSwitchEnabled;
+    private boolean mKeyguardUserSwitcherEnabled;
     private boolean mDozing;
     private boolean mDozingOnDown;
     private int mBarState;
@@ -464,6 +502,7 @@
 
     private final CommandQueue mCommandQueue;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final UserManager mUserManager;
     private final ShadeController mShadeController;
     private final MediaDataManager mMediaDataManager;
     private int mDisplayId;
@@ -556,11 +595,15 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
+            KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
+            KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
+            QSDetailDisplayer qsDetailDisplayer,
             NotificationGroupManagerLegacy groupManager,
             NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
-            QSDetailDisplayer qsDetailDisplayer,
             ScrimController scrimController,
+            UserManager userManager,
             MediaDataManager mediaDataManager,
             AmbientState ambientState,
             FeatureFlags featureFlags,
@@ -581,8 +624,16 @@
         mGroupManager = groupManager;
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
-        mQSDetailDisplayer = qsDetailDisplayer;
+        mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mFeatureFlags = featureFlags;
+        mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
+        mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
+        mQSDetailDisplayer = qsDetailDisplayer;
+        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
+                com.android.internal.R.bool.config_keyguardUserSwitcher);
+        mKeyguardQsUserSwitchEnabled =
+                mKeyguardUserSwitcherEnabled && mResources.getBoolean(
+                        R.bool.config_keyguard_user_switch_opens_qs_details);
         mView.setWillNotDraw(!DEBUG);
         mLayoutInflater = layoutInflater;
         mFalsingManager = falsingManager;
@@ -598,6 +649,7 @@
         mDozeParameters = dozeParameters;
         mBiometricUnlockController = biometricUnlockController;
         mScrimController = scrimController;
+        mUserManager = userManager;
         mMediaDataManager = mediaDataManager;
         mControlsComponent = controlsComponent;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -660,9 +712,25 @@
     private void onFinishInflate() {
         loadDimens();
         mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
-        mKeyguardStatusBar.setQSDetailDisplayer(mQSDetailDisplayer);
         mBigClockContainer = mView.findViewById(R.id.big_clock_container);
-        updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
+
+        UserAvatarView userAvatarView = null;
+        KeyguardUserSwitcherView keyguardUserSwitcherView = null;
+
+        if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
+            if (mKeyguardQsUserSwitchEnabled) {
+                ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
+                userAvatarView = (UserAvatarView) stub.inflate();
+            } else {
+                ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
+                keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
+            }
+        }
+
+        updateViewControllers(mView.findViewById(R.id.keyguard_status_view),
+                userAvatarView,
+                mKeyguardStatusBar,
+                keyguardUserSwitcherView);
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
         NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
                 R.id.notification_stack_scroller);
@@ -707,6 +775,10 @@
         });
 
         mView.setAccessibilityDelegate(mAccessibilityDelegate);
+        // dynamically apply the split shade value overrides.
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+            updateResources();
+        }
     }
 
     @Override
@@ -735,17 +807,57 @@
                 R.dimen.heads_up_status_bar_padding);
     }
 
-    private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+    private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+            UserAvatarView userAvatarView,
+            KeyguardStatusBarView keyguardStatusBarView,
+            KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
         KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
         mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
         mKeyguardStatusViewController.init();
 
+        KeyguardStatusBarViewComponent statusBarViewComponent =
+                mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
+        mKeyguarStatusBarViewController =
+                statusBarViewComponent.getKeyguardStatusBarViewController();
+        mKeyguarStatusBarViewController.init();
+
         // Re-associate the clock container with the keyguard clock switch.
         KeyguardClockSwitchController keyguardClockSwitchController =
                 statusViewComponent.getKeyguardClockSwitchController();
         keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+
+        if (mKeyguardUserSwitcherController != null) {
+            // Try to close the switcher so that callbacks are triggered if necessary.
+            // Otherwise, NPV can get into a state where some of the views are still hidden
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(false);
+            mKeyguardUserSwitcherController.removeCallback();
+        }
+
+        mKeyguardQsUserSwitchController = null;
+        mKeyguardUserSwitcherController = null;
+
+        // Re-associate the KeyguardUserSwitcherController
+        if (userAvatarView != null) {
+            KeyguardQsUserSwitchComponent userSwitcherComponent =
+                    mKeyguardQsUserSwitchComponentFactory.build(userAvatarView);
+            mKeyguardQsUserSwitchController =
+                    userSwitcherComponent.getKeyguardQsUserSwitchController();
+            mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
+            mKeyguardQsUserSwitchController.init();
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+        } else if (keyguardUserSwitcherView != null) {
+            KeyguardUserSwitcherComponent userSwitcherComponent =
+                    mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
+            mKeyguardUserSwitcherController =
+                    userSwitcherComponent.getKeyguardUserSwitcherController();
+            mKeyguardUserSwitcherController.setCallback(mKeyguardUserSwitcherListener);
+            mKeyguardUserSwitcherController.init();
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+        } else {
+            mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+        }
     }
 
     /**
@@ -783,16 +895,21 @@
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
 
-        if (shouldUseSplitNotificationShade()) {
-            // In order to change the constraints at runtime, all children of the Constraint Layout
-            // must have ids.
-            ensureAllViewsHaveIds(mNotificationContainerParent);
+        // In order to change the constraints at runtime, all children of the Constraint Layout
+        // must have ids.
+        ensureAllViewsHaveIds(mNotificationContainerParent);
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+            constraintSet.connect(R.id.qs_frame, END, R.id.qs_edge_guideline, END);
+            constraintSet.connect(
+                    R.id.notification_stack_scroller, START,
+                    R.id.qs_edge_guideline, START);
+        } else {
+            constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
+            constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
         }
-    }
-
-    private boolean shouldUseSplitNotificationShade() {
-        return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
-                && mResources.getBoolean(R.bool.config_use_split_notification_shade);
+        constraintSet.applyTo(mNotificationContainerParent);
     }
 
     private static void ensureAllViewsHaveIds(ViewGroup parentView) {
@@ -804,7 +921,27 @@
         }
     }
 
+    private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
+        View view = mView.findViewById(viewId);
+        if (view != null) {
+            int index = mView.indexOfChild(view);
+            mView.removeView(view);
+            if (enabled) {
+                view = mLayoutInflater.inflate(layoutId, mView, false);
+                mView.addView(view, index);
+            } else {
+                view = null;
+            }
+        } else if (enabled) {
+            // It's possible the stub was never inflated if the configuration changed
+            ViewStub stub = mView.findViewById(stubId);
+            view = stub.inflate();
+        }
+        return view;
+    }
+
     private void reInflateViews() {
+        if (DEBUG) Log.d(TAG, "reInflateViews");
         // Re-inflate the status view group.
         KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
         int index = mView.indexOfChild(keyguardStatusView);
@@ -813,8 +950,28 @@
                 R.layout.keyguard_status_view, mView, false);
         mView.addView(keyguardStatusView, index);
 
+        // Re-inflate the keyguard user switcher group.
+        boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled();
+        boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
+        boolean showKeyguardUserSwitcher =
+                !mKeyguardQsUserSwitchEnabled
+                        && mKeyguardUserSwitcherEnabled
+                        && isUserSwitcherEnabled;
+        UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+                R.id.keyguard_qs_user_switch_view /* viewId */,
+                R.id.keyguard_qs_user_switch_stub /* stubId */,
+                R.layout.keyguard_qs_user_switch /* layoutId */,
+                showQsUserSwitch /* enabled */);
+        KeyguardUserSwitcherView keyguardUserSwitcherView =
+                (KeyguardUserSwitcherView) reInflateStub(
+                        R.id.keyguard_user_switcher_view /* viewId */,
+                        R.id.keyguard_user_switcher_stub /* stubId */,
+                        R.layout.keyguard_user_switcher /* layoutId */,
+                        showKeyguardUserSwitcher /* enabled */);
+
         mBigClockContainer.removeAllViews();
-        updateViewControllers(keyguardStatusView);
+        updateViewControllers(
+                keyguardStatusView, userAvatarView, mKeyguardStatusBar, keyguardUserSwitcherView);
 
         // Update keyguard bottom area
         index = mView.indexOfChild(mKeyguardBottomArea);
@@ -838,6 +995,20 @@
                 false,
                 false,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    false,
+                    false,
+                    mBarState);
+        }
         setKeyguardBottomAreaVisibility(mBarState, false);
     }
 
@@ -930,10 +1101,15 @@
             int totalHeight = mView.getHeight();
             int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
             int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight);
+            int userSwitcherPreferredY = mStatusBarMinHeight;
             boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
             final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
                     .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
             mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
+            int userIconHeight = mKeyguardQsUserSwitchController != null
+                    ? mKeyguardQsUserSwitchController.getUserIconHeight()
+                    : (mKeyguardUserSwitcherController != null
+                            ? mKeyguardUserSwitcherController.getUserIconHeight() : 0);
             mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
                     mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
                     getExpandedFraction(),
@@ -942,7 +1118,8 @@
                             ? mKeyguardStatusViewController.getHeight()
                             : (int) (mKeyguardStatusViewController.getHeight()
                                     - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
-                    clockPreferredY, hasCustomClock(),
+                    userIconHeight,
+                    clockPreferredY, userSwitcherPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
                     mUpdateMonitor.canShowLockIcon(),
@@ -952,6 +1129,18 @@
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY,
                     mClockPositionResult.clockScale, animateClock);
+            if (mKeyguardQsUserSwitchController != null) {
+                mKeyguardQsUserSwitchController.updatePosition(
+                        mClockPositionResult.clockX,
+                        mClockPositionResult.userSwitchY,
+                        animateClock);
+            }
+            if (mKeyguardUserSwitcherController != null) {
+                mKeyguardUserSwitcherController.updatePosition(
+                        mClockPositionResult.clockX,
+                        mClockPositionResult.userSwitchY,
+                        animateClock);
+            }
             updateNotificationTranslucency();
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1092,6 +1281,12 @@
 
     private void updateClock() {
         mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setAlpha(mClockPositionResult.clockAlpha);
+        }
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setAlpha(mClockPositionResult.clockAlpha);
+        }
     }
 
     public void animateToFullShade(long delay) {
@@ -1180,6 +1375,12 @@
         }
     }
 
+    public void expandWithQsDetail(DetailAdapter qsDetailAdapter) {
+        traceQsJank(true /* startTracing */, false /* wasCancelled */);
+        flingSettings(0 /* velocity */, FLING_EXPAND);
+        mQSDetailDisplayer.showDetailAdapter(qsDetailAdapter, 0, 0);
+    }
+
     public void expandWithoutQs() {
         if (isQsExpanded()) {
             flingSettings(0 /* velocity */, FLING_COLLAPSE);
@@ -1773,8 +1974,9 @@
                 mBarState != KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
 
-        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
-            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+        if (mKeyguardUserSwitcherController != null && mQsExpanded
+                && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true);
         }
         if (mQs == null) return;
         mQs.setExpanded(mQsExpanded);
@@ -1841,6 +2043,10 @@
     }
 
     private float calculateQsTopPadding() {
+        // in split shade mode we want notifications to be directly below status bar
+        if (Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources) && !mKeyguardShowing) {
+            return 0f;
+        }
         if (mKeyguardShowing && (mQsExpandImmediate
                 || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
 
@@ -2480,7 +2686,9 @@
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
-            mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+            if (!Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources)) {
+                mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
+            }
         }
         if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
             mAffordanceHelper.animateHideLeftRightIcon();
@@ -2608,10 +2816,6 @@
         }
     }
 
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
     public void onScreenTurningOn() {
         mKeyguardStatusViewController.dozeTimeTick();
     }
@@ -2726,12 +2930,12 @@
     }
 
     /**
-     * Updates the vertical position of the panel so it is positioned closer to the touch
+     * Updates the horizontal position of the panel so it is positioned closer to the touch
      * responsible for opening the panel.
      *
      * @param x the x-coordinate the touch event
      */
-    protected void updateVerticalPanelPosition(float x) {
+    protected void updateHorizontalPanelPosition(float x) {
         if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()) {
             resetHorizontalPanelPosition();
             return;
@@ -3078,6 +3282,20 @@
                 true /* keyguardFadingAway */,
                 false /* goingToFullShade */,
                 mBarState);
+        if (mKeyguardQsUserSwitchController != null) {
+            mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
+        if (mKeyguardUserSwitcherController != null) {
+            mKeyguardUserSwitcherController.setKeyguardUserSwitcherVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    false /* goingToFullShade */,
+                    mBarState);
+        }
     }
 
     /**
@@ -3301,7 +3519,7 @@
                 }
                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                     mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
-                    updateVerticalPanelPosition(event.getX());
+                    updateHorizontalPanelPosition(event.getX());
                     handled = true;
                 }
                 handled |= super.onTouch(v, event);
@@ -3320,6 +3538,50 @@
         return mNotificationStackScrollLayoutController;
     }
 
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or
+     * if the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    public boolean closeUserSwitcherIfOpen() {
+        if (mKeyguardUserSwitcherController != null) {
+            return mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
+                    true /* animate */);
+        }
+        return false;
+    }
+
+    private void updateUserSwitcherVisibility(boolean open) {
+        // Do not update if previously called with the same state.
+        if (mKeyguardUserSwitcherIsShowing == open) {
+            return;
+        }
+        mKeyguardUserSwitcherIsShowing = open;
+
+        if (open) {
+            animateKeyguardStatusBarOut();
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    mBarState,
+                    true /* keyguardFadingAway */,
+                    true /* goingToFullShade */,
+                    mBarState);
+            setKeyguardBottomAreaVisibility(mBarState, true);
+            mNotificationContainerParent.setVisibility(View.GONE);
+        } else {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    StatusBarState.KEYGUARD,
+                    false,
+                    false,
+                    StatusBarState.SHADE_LOCKED);
+            setKeyguardBottomAreaVisibility(mBarState, false);
+            mNotificationContainerParent.setVisibility(View.VISIBLE);
+        }
+    }
+
     private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -3620,6 +3882,7 @@
     private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
         @Override
         public void onThemeChanged() {
+            if (DEBUG) Log.d(TAG, "onThemeChanged");
             final int themeResId = mView.getContext().getThemeResId();
             if (mThemeResId == themeResId) {
                 return;
@@ -3631,11 +3894,15 @@
 
         @Override
         public void onOverlayChanged() {
+            if (DEBUG) Log.d(TAG, "onOverlayChanged");
             reInflateViews();
         }
 
         @Override
-        public void onUiModeChanged() {}
+        public void onDensityOrFontScaleChanged() {
+            if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
+            reInflateViews();
+        }
     }
 
     private class StatusBarStateListener implements StateListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index b367406..e394ebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,8 +22,6 @@
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewStub;
-import android.view.ViewStub.OnInflateListener;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
 
@@ -44,14 +42,11 @@
  * The container with notification stack scroller and quick settings inside.
  */
 public class NotificationsQuickSettingsContainer extends ConstraintLayout
-        implements OnInflateListener, FragmentListener,
-        AboveShelfObserver.HasViewAboveShelfChangedListener {
+        implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
 
     private FrameLayout mQsFrame;
-    private View mUserSwitcher;
     private NotificationStackScrollLayout mStackScroller;
     private View mKeyguardStatusBar;
-    private boolean mInflated;
     private boolean mQsExpanded;
     private boolean mCustomizerAnimating;
 
@@ -73,9 +68,6 @@
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
-        ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
-        userSwitcher.setOnInflateListener(this);
-        mUserSwitcher = userSwitcher;
     }
 
     @Override
@@ -119,10 +111,6 @@
         // touches first but the panel gets drawn above.
         mDrawingOrderedChildren.clear();
         mLayoutDrawingOrder.clear();
-        if (mInflated && mUserSwitcher.getVisibility() == View.VISIBLE) {
-            mDrawingOrderedChildren.add(mUserSwitcher);
-            mLayoutDrawingOrder.add(mUserSwitcher);
-        }
         if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
             mDrawingOrderedChildren.add(mKeyguardStatusBar);
             mLayoutDrawingOrder.add(mKeyguardStatusBar);
@@ -158,14 +146,6 @@
     }
 
     @Override
-    public void onInflate(ViewStub stub, View inflated) {
-        if (stub == mUserSwitcher) {
-            mUserSwitcher = inflated;
-            mInflated = true;
-        }
-    }
-
-    @Override
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         QS container = (QS) fragment;
         container.setContainer(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1e19bee..041a97e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -89,7 +89,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -226,7 +225,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
@@ -623,7 +621,6 @@
         }
     };
 
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private final UserSwitcherController mUserSwitcherController;
     private final NetworkController mNetworkController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -1212,9 +1209,6 @@
         });
 
         mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
-        if (UserManager.get(mContext).isUserSwitcherEnabled()) {
-            createUserSwitcher();
-        }
 
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
@@ -1441,9 +1435,6 @@
         // TODO: Bring these out of StatusBar.
         mUserInfoControllerImpl.onDensityOrFontScaleChanged();
         mUserSwitcherController.onDensityOrFontScaleChanged();
-        if (mKeyguardUserSwitcher != null) {
-            mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
-        }
         mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
         mHeadsUpManager.onDensityOrFontScaleChanged();
     }
@@ -1477,13 +1468,6 @@
         }
     }
 
-    protected void createUserSwitcher() {
-        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_user_switcher),
-                mNotificationShadeWindowView.findViewById(R.id.keyguard_header),
-                mNotificationPanelViewController);
-    }
-
     private void inflateStatusBarWindow() {
         mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
         StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
@@ -3266,7 +3250,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
             mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
-        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()){
+        } else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
             mStatusBarStateController.setState(StatusBarState.KEYGUARD);
         }
         updatePanelExpansionForKeyguard();
@@ -3565,15 +3549,15 @@
             }
             return true;
         }
+        if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
+            return true;
+        }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             }
             return true;
         }
-        if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) {
-            return true;
-        }
         return false;
     }
 
@@ -3622,20 +3606,8 @@
         updateTheme();
         mNavigationBarController.touchAutoDim(mDisplayId);
         Trace.beginSection("StatusBar#updateKeyguardState");
-        if (mState == StatusBarState.KEYGUARD) {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(true,
-                        mStatusBarStateController.fromShadeLocked());
-            }
-            if (mStatusBarView != null) mStatusBarView.removePendingHideExpandedRunnables();
-        } else {
-            if (mKeyguardUserSwitcher != null) {
-                mKeyguardUserSwitcher.setKeyguard(false,
-                        mStatusBarStateController.goingToFullShade() ||
-                                mState == StatusBarState.SHADE_LOCKED ||
-                                mStatusBarStateController.fromShadeLocked());
-            }
-
+        if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+            mStatusBarView.removePendingHideExpandedRunnables();
         }
         updateDozingState();
         checkBarModes();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 00acd7b..8620376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -42,8 +42,8 @@
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
@@ -66,7 +66,7 @@
     /**
      * Display the no calling & SMS icons.
      */
-    void setNoCallingIcons(String slot, List<NoCallingIconState> states);
+    void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states);
     public void setIconVisibility(String slot, boolean b);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 5e8d590..f0c8527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -34,8 +34,8 @@
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -206,6 +206,7 @@
         Collections.reverse(iconStates);
 
         for (MobileIconState state : iconStates) {
+
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -218,23 +219,25 @@
     }
 
     /**
-     * Accept a list of NoCallingIconStates, and show them in the same slot
+     * Accept a list of CallIndicatorIconStates, and show them in the same slot
      * @param slot StatusBar slot
      * @param states All of the no Calling & SMS icon states
      */
     @Override
-    public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+    public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
         Slot noCallingSlot = getSlot(slot);
         int slotIndex = getSlotIndex(slot);
-
-        for (NoCallingIconState state : states) {
+        for (CallIndicatorIconState state : states) {
             StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
             if (holder == null) {
-                holder = StatusBarIconHolder.fromNoCallingState(mContext, state);
-                holder.setVisible(state.visible);
+                holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
                 setIcon(slotIndex, holder);
             } else {
-                holder.setVisible(state.visible);
+                int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+                String contentDescription = state.isNoCalling
+                        ? state.noCallingDescription : state.callStrengthDescription;
+                holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
+                        Icon.createWithResource(mContext, resId), 0, 0, contentDescription));
                 setIcon(slotIndex, holder);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 36a0e63..a1a2d30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -22,8 +22,8 @@
 import android.os.UserHandle;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 /**
@@ -72,14 +72,18 @@
     }
 
     /**
-     * Creates a new StatusBarIconHolder from a NoCallingIconState.
+     * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
      */
-    public static StatusBarIconHolder fromNoCallingState(
-            Context context, NoCallingIconState state) {
+    public static StatusBarIconHolder fromCallIndicatorState(
+            Context context, CallIndicatorIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
+        int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
+        String contentDescription = state.isNoCalling
+                ? state.noCallingDescription : state.callStrengthDescription;
         holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource(context, state.resId), 0, 0, null);
+                Icon.createWithResource(context, resId), 0, 0, contentDescription);
         holder.mTag = state.subId;
+        holder.setVisible(true);
         return holder;
     }
 
@@ -92,6 +96,10 @@
         return mIcon;
     }
 
+    public void setIcon(StatusBarIcon icon) {
+        mIcon = icon;
+    }
+
     @Nullable
     public WifiIconState getWifiState() {
         return mWifiState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index f6165f6..7bc1bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -22,6 +22,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -39,7 +40,7 @@
 public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallback,
         SecurityController.SecurityControllerCallback, Tunable {
     private static final String TAG = "StatusBarSignalPolicy";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.INFO);
 
     private final String mSlotAirplane;
     private final String mSlotMobile;
@@ -67,7 +68,8 @@
     private boolean mWifiVisible = false;
 
     private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
-    private ArrayList<NoCallingIconState> mNoCallingStates = new ArrayList<NoCallingIconState>();
+    private ArrayList<CallIndicatorIconState> mCallIndicatorStates =
+            new ArrayList<CallIndicatorIconState>();
     private WifiIconState mWifiIconState = new WifiIconState();
 
     public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
@@ -201,19 +203,25 @@
     }
 
     @Override
-    public void setNoCallingStatus(boolean noCalling, int subId) {
+    public void setCallIndicator(IconState statusIcon, int subId) {
         if (DEBUG) {
-            Log.d(TAG, "setNoCallingStatus: "
-                    + "noCalling = " + noCalling + ","
+            Log.d(TAG, "setCallIndicator: "
+                    + "statusIcon = " + statusIcon + ","
                     + "subId = " + subId);
         }
-        NoCallingIconState state = getNoCallingState(subId);
+        CallIndicatorIconState state = getNoCallingState(subId);
         if (state == null) {
             return;
         }
-        state.visible = noCalling;
-        mIconController.setNoCallingIcons(
-                mSlotNoCalling, NoCallingIconState.copyStates(mNoCallingStates));
+        if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+            state.isNoCalling = statusIcon.visible;
+            state.noCallingDescription = statusIcon.contentDescription;
+        } else {
+            state.callStrengthResId = statusIcon.icon;
+            state.callStrengthDescription = statusIcon.contentDescription;
+        }
+        mIconController.setCallIndicatorIcons(
+                mSlotNoCalling, CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
 
     @Override
@@ -273,8 +281,8 @@
         }
     }
 
-    private NoCallingIconState getNoCallingState(int subId) {
-        for (NoCallingIconState state : mNoCallingStates) {
+    private CallIndicatorIconState getNoCallingState(int subId) {
+        for (CallIndicatorIconState state : mCallIndicatorStates) {
             if (state.subId == subId) {
                 return state;
             }
@@ -315,23 +323,25 @@
         }
 
         mIconController.removeAllIconsForSlot(mSlotMobile);
+        mIconController.removeAllIconsForSlot(mSlotNoCalling);
         mMobileStates.clear();
-        List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
-        noCallingStates.addAll(mNoCallingStates);
-        mNoCallingStates.clear();
+        List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
+        noCallingStates.addAll(mCallIndicatorStates);
+        mCallIndicatorStates.clear();
         final int n = subs.size();
         for (int i = 0; i < n; i++) {
             mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
             boolean isNewSub = true;
-            for (NoCallingIconState state : noCallingStates) {
+            for (CallIndicatorIconState state : noCallingStates) {
                 if (state.subId == subs.get(i).getSubscriptionId()) {
-                    mNoCallingStates.add(state);
+                    mCallIndicatorStates.add(state);
                     isNewSub = false;
                     break;
                 }
             }
             if (isNewSub) {
-                mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+                mCallIndicatorStates.add(
+                        new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
             }
         }
     }
@@ -425,14 +435,18 @@
     /**
      * Stores the StatusBar state for no Calling & SMS.
      */
-    public static class NoCallingIconState {
-        public boolean visible;
-        public int resId;
+    public static class CallIndicatorIconState {
+        public boolean isNoCalling;
+        public int noCallingResId;
+        public int callStrengthResId;
         public int subId;
+        public String noCallingDescription;
+        public String callStrengthDescription;
 
-        private NoCallingIconState(int subId) {
+        private CallIndicatorIconState(int subId) {
             this.subId = subId;
-            this.resId = R.drawable.ic_qs_no_calling_sms;
+            this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+            this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
         }
 
         @Override
@@ -441,27 +455,36 @@
             if (o == null || getClass() != o.getClass()) {
                 return false;
             }
-            NoCallingIconState that = (NoCallingIconState) o;
-            return visible == that.visible
-                    && resId == that.resId
-                    && subId == that.subId;
+            CallIndicatorIconState that = (CallIndicatorIconState) o;
+            return  isNoCalling == that.isNoCalling
+                    && noCallingResId == that.noCallingResId
+                    && callStrengthResId == that.callStrengthResId
+                    && subId == that.subId
+                    && noCallingDescription == that.noCallingDescription
+                    && callStrengthDescription == that.callStrengthDescription;
+
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(visible, resId, subId);
+            return Objects.hash(isNoCalling, noCallingResId,
+                    callStrengthResId, subId, noCallingDescription, callStrengthDescription);
         }
 
-        private void copyTo(NoCallingIconState other) {
-            other.visible = visible;
-            other.resId = resId;
+        private void copyTo(CallIndicatorIconState other) {
+            other.isNoCalling = isNoCalling;
+            other.noCallingResId = noCallingResId;
+            other.callStrengthResId = callStrengthResId;
             other.subId = subId;
+            other.noCallingDescription = noCallingDescription;
+            other.callStrengthDescription = callStrengthDescription;
         }
 
-        private static List<NoCallingIconState> copyStates(List<NoCallingIconState> inStates) {
-            ArrayList<NoCallingIconState> outStates = new ArrayList<>();
-            for (NoCallingIconState state : inStates) {
-                NoCallingIconState copy = new NoCallingIconState(state.subId);
+        private static List<CallIndicatorIconState> copyStates(
+                List<CallIndicatorIconState> inStates) {
+            ArrayList<CallIndicatorIconState> outStates = new ArrayList<>();
+            for (CallIndicatorIconState state : inStates) {
+                CallIndicatorIconState copy = new CallIndicatorIconState(state.subId);
                 state.copyTo(copy);
                 outStates.add(copy);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index ccaa1f4..5ff8970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -36,6 +36,7 @@
  * the current or specified Looper.
  */
 public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+    private static final String TAG = "CallbackHandler";
     private static final int MSG_EMERGENCE_CHANGED           = 0;
     private static final int MSG_SUBS_CHANGED                = 1;
     private static final int MSG_NO_SIM_VISIBLE_CHANGED      = 2;
@@ -198,17 +199,17 @@
     }
 
     @Override
-    public void setNoCallingStatus(boolean noCalling, int subId) {
+    public void setCallIndicator(IconState statusIcon, int subId) {
         String log = new StringBuilder()
                 .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setNoCallingStatus: ")
-                .append("noCalling=").append(noCalling).append(",")
+                .append("setCallIndicator: ")
+                .append("statusIcon=").append(statusIcon).append(",")
                 .append("subId=").append(subId)
                 .toString();
         recordLastCallback(log);
         post(() -> {
             for (SignalCallback signalCluster : mSignalCallbacks) {
-                signalCluster.setNoCallingStatus(noCalling, subId);
+                signalCluster.setCallIndicator(statusIcon, subId);
             }
         });
     }
@@ -226,24 +227,11 @@
 
     @Override
     public void setNoSims(boolean show, boolean simDetected) {
-        String log = new StringBuilder()
-                .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setNoSims: ")
-                .append("show=").append(show).append(",")
-                .append("simDetected=").append(simDetected)
-                .toString();
-        recordLastCallback(log);
         obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
     public void setMobileDataEnabled(boolean enabled) {
-        String log = new StringBuilder()
-                .append(SSDF.format(System.currentTimeMillis())).append(",")
-                .append("setMobileDataEnabled: ")
-                .append("enabled=").append(enabled)
-                .toString();
-        recordLastCallback(log);
         obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
     }
 
@@ -283,7 +271,8 @@
     }
 
     protected void recordLastCallback(String callback) {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+        mHistory[mHistoryIndex] = callback;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     /**
@@ -293,7 +282,9 @@
         pw.println("  - CallbackHandler -----");
         int size = 0;
         for (int i = 0; i < HISTORY_SIZE; i++) {
-            if (mHistory[i] != null) size++;
+            if (mHistory[i] != null) {
+                size++;
+            }
         }
         // Print out the previous states in ordered number.
         for (int i = mHistoryIndex + HISTORY_SIZE - 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
new file mode 100644
index 0000000..38f3bc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switch on the Keyguard that is used for opening the QS user panel.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+
+    private static final String TAG = "KeyguardQsUserSwitchController";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final AnimationProperties ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+    private final Context mContext;
+    private Resources mResources;
+    private final UserSwitcherController mUserSwitcherController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private UserSwitcherController.BaseUserAdapter mAdapter;
+    private final KeyguardStateController mKeyguardStateController;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+    private final KeyguardUserDetailAdapter mUserDetailAdapter;
+    private NotificationPanelViewController mNotificationPanelViewController;
+    private UserManager mUserManager;
+    UserSwitcherController.UserRecord mCurrentUser;
+
+    // State info for the user switch and keyguard
+    private int mBarState;
+    private float mDarkAmount;
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+                    int oldState = mBarState;
+                    mBarState = newState;
+
+                    setKeyguardQsUserSwitchVisibility(
+                            newState,
+                            keyguardFadingAway,
+                            goingToFullShade,
+                            oldState);
+                }
+
+                @Override
+                public void onDozeAmountChanged(float linearAmount, float amount) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+                                linearAmount, amount));
+                    }
+                    setDarkAmount(amount);
+                }
+            };
+
+    @Inject
+    public KeyguardQsUserSwitchController(
+            UserAvatarView view,
+            Context context,
+            @Main Resources resources,
+            UserManager userManager,
+            ScreenLifecycle screenLifecycle,
+            UserSwitcherController userSwitcherController,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            DozeParameters dozeParameters,
+            UiEventLogger uiEventLogger) {
+        super(view);
+        if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
+        mContext = context;
+        mResources = resources;
+        mUserManager = userManager;
+        mScreenLifecycle = screenLifecycle;
+        mUserSwitcherController = userSwitcherController;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+                keyguardStateController, dozeParameters);
+        mUserDetailAdapter = new KeyguardUserDetailAdapter(mUserSwitcherController, mContext,
+                uiEventLogger);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        if (DEBUG) Log.d(TAG, "onInit");
+        mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                return null;
+            }
+        };
+
+        mView.setOnClickListener(v -> {
+            if (isListAnimating()) {
+                return;
+            }
+
+            // Tapping anywhere in the view will open QS user panel
+            openQsUserPanel();
+        });
+
+        updateView(true /* forceUpdate */);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        if (DEBUG) Log.d(TAG, "onViewAttached");
+        mAdapter.registerDataSetObserver(mDataSetObserver);
+        mDataSetObserver.onChanged();
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        if (DEBUG) Log.d(TAG, "onViewDetached");
+
+        mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+    }
+
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            updateView(false /* forceUpdate */);
+        }
+    };
+
+    /**
+     * @return true if the current user has changed
+     */
+    private boolean updateCurrentUser() {
+        UserSwitcherController.UserRecord previousUser = mCurrentUser;
+        mCurrentUser = null;
+        for (int i = 0; i < mAdapter.getCount(); i++) {
+            UserSwitcherController.UserRecord r = mAdapter.getItem(i);
+            if (r.isCurrent) {
+                mCurrentUser = r;
+                return !mCurrentUser.equals(previousUser);
+            }
+        }
+        return mCurrentUser == null && previousUser != null;
+    }
+
+    /**
+     * @param forceUpdate whether to update view even if current user did not change
+     */
+    private void updateView(boolean forceUpdate) {
+        if (!updateCurrentUser() && !forceUpdate) {
+            return;
+        }
+
+        if (mCurrentUser == null) {
+            mView.setVisibility(View.GONE);
+            return;
+        }
+
+        mView.setVisibility(View.VISIBLE);
+
+        String currentUserName = mCurrentUser.info.name;
+        String contentDescription = null;
+
+        if (!TextUtils.isEmpty(currentUserName)) {
+            contentDescription = mContext.getString(
+                    R.string.accessibility_quick_settings_user,
+                    currentUserName);
+        }
+
+        if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
+            mView.setContentDescription(contentDescription);
+        }
+
+        if (mCurrentUser.picture == null) {
+            mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(),
+                    mCurrentUser.resolveId());
+        } else {
+            int avatarSize =
+                    (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+            Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
+            drawable.setColorFilter(
+                    mCurrentUser.isSwitchToEnabled ? null
+                            : mAdapter.getDisabledUserAvatarColorFilter());
+            mView.setDrawableWithBadge(drawable, mCurrentUser.info.id);
+        }
+    }
+
+    Drawable getDrawable(UserSwitcherController.UserRecord item) {
+        Drawable drawable;
+        if (item.isCurrent && item.isGuest) {
+            drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+        } else {
+            drawable = mAdapter.getIconDrawable(mContext, item);
+        }
+
+        int iconColorRes;
+        if (item.isSwitchToEnabled) {
+            iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+        } else {
+            iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+        }
+        drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+        Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+        drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+        return drawable;
+    }
+
+    /**
+     * Get the height of the keyguard user switcher view when closed.
+     */
+    public int getUserIconHeight() {
+        return mView.getHeight();
+    }
+
+    /**
+     * Set the visibility of the user avatar view based on some new state.
+     */
+    public void setKeyguardQsUserSwitchVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+    }
+
+    /**
+     * Update position of the view with an optional animation
+     */
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
+        PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+                ANIMATION_PROPERTIES, animate);
+    }
+
+    /**
+     * Set keyguard user avatar view alpha.
+     */
+    public void setAlpha(float alpha) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+            mView.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    private void setDarkAmount(float darkAmount) {
+        boolean isAwake = darkAmount != 0;
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+    }
+
+    private boolean isListAnimating() {
+        return mKeyguardVisibilityHelper.isVisibilityAnimating();
+    }
+
+    private void openQsUserPanel() {
+        mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
+    }
+
+    public void setNotificationPanelViewController(
+            NotificationPanelViewController notificationPanelViewController) {
+        mNotificationPanelViewController = notificationPanelViewController;
+    }
+
+    class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
+        KeyguardUserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+                UiEventLogger uiEventLogger) {
+            super(userSwitcherController, context, uiEventLogger);
+        }
+
+        @Override
+        public boolean shouldAnimate() {
+            return false;
+        }
+
+        @Override
+        public boolean onDoneButtonClicked() {
+            if (mNotificationPanelViewController != null) {
+                mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 07433e1..0649478 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -17,8 +17,15 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Context;
+import android.graphics.Color;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
 
+import androidx.core.graphics.ColorUtils;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 
@@ -27,6 +34,14 @@
  */
 public class KeyguardUserDetailItemView extends UserDetailItemView {
 
+    private static final String TAG = "KeyguardUserDetailItemView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_FADE_NAME = 240;
+
+    private float mDarkAmount;
+    private int mTextColor;
+
     public KeyguardUserDetailItemView(Context context) {
         this(context, null);
     }
@@ -48,4 +63,89 @@
     protected int getFontSizeDimen() {
         return R.dimen.kg_user_switcher_text_size;
     }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTextColor = mName.getCurrentTextColor();
+        updateDark();
+    }
+
+    /**
+     * Update visibility of this view.
+     *
+     * @param showItem If true, this item is visible on the screen to the user. Generally this
+     *                 means that the item would be clickable. If false, item visibility will be
+     *                 set to GONE and hidden entirely.
+     * @param showTextName Whether or not the name should be shown next to the icon. If false,
+     *                     only the icon is shown.
+     * @param animate Whether the transition should be animated. Note, this only applies to
+     *                animating the text name. The item itself will not animate (i.e. fade in/out).
+     *                Instead, we delegate that to the parent view.
+     */
+    void updateVisibilities(boolean showItem, boolean showTextName, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities itemIsShown=%b nameIsShown=%b animate=%b",
+                    showItem, showTextName, animate));
+        }
+
+        getBackground().setAlpha((showItem && showTextName) ? 255 : 0);
+
+        if (showItem) {
+            if (showTextName) {
+                mName.setVisibility(View.VISIBLE);
+                if (animate) {
+                    mName.setAlpha(0f);
+                    mName.animate()
+                            .alpha(1f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_IN);
+                } else {
+                    mName.setAlpha(1f);
+                }
+            } else {
+                if (animate) {
+                    mName.setVisibility(View.VISIBLE);
+                    mName.setAlpha(1f);
+                    mName.animate()
+                            .alpha(0f)
+                            .setDuration(ANIMATION_DURATION_FADE_NAME)
+                            .setInterpolator(Interpolators.ALPHA_OUT)
+                            .withEndAction(() -> {
+                                mName.setVisibility(View.GONE);
+                                mName.setAlpha(1f);
+                            });
+                } else {
+                    mName.setVisibility(View.GONE);
+                    mName.setAlpha(1f);
+                }
+            }
+            setVisibility(View.VISIBLE);
+            setAlpha(1f);
+        } else {
+            // If item isn't shown, don't animate. The parent class will animate the view instead
+            setVisibility(View.GONE);
+            setAlpha(1f);
+            mName.setVisibility(showTextName ? View.VISIBLE : View.GONE);
+            mName.setAlpha(1f);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    public void setDarkAmount(float darkAmount) {
+        if (mDarkAmount == darkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        updateDark();
+    }
+
+    private void updateDark() {
+        final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mName.setTextColor(blendedTextColor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
deleted file mode 100644
index 90f5577..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
-import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.FrameLayout;
-
-import com.android.settingslib.animation.AppearAnimationUtils;
-import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.qs.tiles.UserDetailItemView;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-
-import java.util.ArrayList;
-
-/**
- * Manages the user switcher on the Keyguard.
- */
-public class KeyguardUserSwitcher {
-
-    private static final String TAG = "KeyguardUserSwitcher";
-    private static final boolean ALWAYS_ON = false;
-
-    private final Container mUserSwitcherContainer;
-    private final KeyguardStatusBarView mStatusBarView;
-    private final KeyguardUserAdapter mAdapter;
-    private final AppearAnimationUtils mAppearAnimationUtils;
-    private final KeyguardUserSwitcherScrim mBackground;
-
-    private ViewGroup mUserSwitcher;
-    private ObjectAnimator mBgAnimator;
-    private UserSwitcherController mUserSwitcherController;
-    private boolean mAnimating;
-
-    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView,
-            NotificationPanelViewController panelViewController) {
-        boolean keyguardUserSwitcherEnabled =
-                context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
-        UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
-        if (userSwitcherController != null && keyguardUserSwitcherEnabled) {
-            mUserSwitcherContainer = (Container) userSwitcher.inflate();
-            mBackground = new KeyguardUserSwitcherScrim(context);
-            reinflateViews();
-            mStatusBarView = statusBarView;
-            mStatusBarView.setKeyguardUserSwitcher(this);
-            panelViewController.setKeyguardUserSwitcher(this);
-            mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this);
-            mAdapter.registerDataSetObserver(mDataSetObserver);
-            mUserSwitcherController = userSwitcherController;
-            mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f,
-                    Interpolators.FAST_OUT_SLOW_IN);
-            mUserSwitcherContainer.setKeyguardUserSwitcher(this);
-        } else {
-            mUserSwitcherContainer = null;
-            mStatusBarView = null;
-            mAdapter = null;
-            mAppearAnimationUtils = null;
-            mBackground = null;
-        }
-    }
-
-    private void reinflateViews() {
-        if (mUserSwitcher != null) {
-            mUserSwitcher.setBackground(null);
-            mUserSwitcher.removeOnLayoutChangeListener(mBackground);
-        }
-        mUserSwitcherContainer.removeAllViews();
-
-        LayoutInflater.from(mUserSwitcherContainer.getContext())
-                .inflate(R.layout.keyguard_user_switcher_inner, mUserSwitcherContainer);
-
-        mUserSwitcher = (ViewGroup) mUserSwitcherContainer.findViewById(
-                R.id.keyguard_user_switcher_inner);
-        mUserSwitcher.addOnLayoutChangeListener(mBackground);
-        mUserSwitcher.setBackground(mBackground);
-    }
-
-    public void setKeyguard(boolean keyguard, boolean animate) {
-        if (mUserSwitcher != null) {
-            if (keyguard && shouldExpandByDefault()) {
-                show(animate);
-            } else {
-                hide(animate);
-            }
-        }
-    }
-
-    /**
-     * @return true if the user switcher should be expanded by default on the lock screen.
-     * @see android.os.UserManager#isUserSwitcherEnabled()
-     */
-    private boolean shouldExpandByDefault() {
-        return (mUserSwitcherController != null) && mUserSwitcherController.isSimpleUserSwitcher();
-    }
-
-    public void show(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() != View.VISIBLE) {
-            cancelAnimations();
-            mAdapter.refresh();
-            mUserSwitcherContainer.setVisibility(View.VISIBLE);
-            mStatusBarView.setKeyguardUserSwitcherShowing(true, animate);
-            if (animate) {
-                startAppearAnimation();
-            }
-        }
-    }
-
-    private boolean hide(boolean animate) {
-        if (mUserSwitcher != null && mUserSwitcherContainer.getVisibility() == View.VISIBLE) {
-            cancelAnimations();
-            if (animate) {
-                startDisappearAnimation();
-            } else {
-                mUserSwitcherContainer.setVisibility(View.GONE);
-            }
-            mStatusBarView.setKeyguardUserSwitcherShowing(false, animate);
-            return true;
-        }
-        return false;
-    }
-
-    private void cancelAnimations() {
-        int count = mUserSwitcher.getChildCount();
-        for (int i = 0; i < count; i++) {
-            mUserSwitcher.getChildAt(i).animate().cancel();
-        }
-        if (mBgAnimator != null) {
-            mBgAnimator.cancel();
-        }
-        mUserSwitcher.animate().cancel();
-        mAnimating = false;
-    }
-
-    private void startAppearAnimation() {
-        int count = mUserSwitcher.getChildCount();
-        View[] objects = new View[count];
-        for (int i = 0; i < count; i++) {
-            objects[i] = mUserSwitcher.getChildAt(i);
-        }
-        mUserSwitcher.setClipChildren(false);
-        mUserSwitcher.setClipToPadding(false);
-        mAppearAnimationUtils.startAnimation(objects, new Runnable() {
-            @Override
-            public void run() {
-                mUserSwitcher.setClipChildren(true);
-                mUserSwitcher.setClipToPadding(true);
-            }
-        });
-        mAnimating = true;
-        mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
-        mBgAnimator.setDuration(400);
-        mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mBgAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBgAnimator = null;
-                mAnimating = false;
-            }
-        });
-        mBgAnimator.start();
-    }
-
-    private void startDisappearAnimation() {
-        mAnimating = true;
-        mUserSwitcher.animate()
-                .alpha(0f)
-                .setDuration(300)
-                .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mUserSwitcherContainer.setVisibility(View.GONE);
-                        mUserSwitcher.setAlpha(1f);
-                        mAnimating = false;
-                    }
-                });
-    }
-
-    private void refresh() {
-        final int childCount = mUserSwitcher.getChildCount();
-        final int adapterCount = mAdapter.getCount();
-        final int N = Math.max(childCount, adapterCount);
-        for (int i = 0; i < N; i++) {
-            if (i < adapterCount) {
-                View oldView = null;
-                if (i < childCount) {
-                    oldView = mUserSwitcher.getChildAt(i);
-                }
-                View newView = mAdapter.getView(i, oldView, mUserSwitcher);
-                if (oldView == null) {
-                    // We ran out of existing views. Add it at the end.
-                    mUserSwitcher.addView(newView);
-                } else if (oldView != newView) {
-                    // We couldn't rebind the view. Replace it.
-                    mUserSwitcher.removeViewAt(i);
-                    mUserSwitcher.addView(newView, i);
-                }
-            } else {
-                int lastIndex = mUserSwitcher.getChildCount() - 1;
-                mUserSwitcher.removeViewAt(lastIndex);
-            }
-        }
-    }
-
-    public boolean hideIfNotSimple(boolean animate) {
-        if (mUserSwitcherContainer != null && !mUserSwitcherController.isSimpleUserSwitcher()) {
-            return hide(animate);
-        }
-        return false;
-    }
-
-    boolean isAnimating() {
-        return mAnimating;
-    }
-
-    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
-        @Override
-        public void onChanged() {
-            refresh();
-        }
-    };
-
-    public void onDensityOrFontScaleChanged() {
-        if (mUserSwitcherContainer != null) {
-            reinflateViews();
-            refresh();
-        }
-    }
-
-    static class KeyguardUserAdapter extends
-            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
-
-        private Context mContext;
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-        private View mCurrentUserView;
-        // List of users where the first entry is always the current user
-        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
-
-        KeyguardUserAdapter(Context context, UserSwitcherController controller,
-                KeyguardUserSwitcher kgu) {
-            super(controller);
-            mContext = context;
-            mKeyguardUserSwitcher = kgu;
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            refreshUserOrder();
-            super.notifyDataSetChanged();
-        }
-
-        void refreshUserOrder() {
-            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
-            mUsersOrdered = new ArrayList<>(users.size());
-            for (int i = 0; i < users.size(); i++) {
-                UserSwitcherController.UserRecord record = users.get(i);
-                if (record.isCurrent) {
-                    mUsersOrdered.add(0, record);
-                } else {
-                    mUsersOrdered.add(record);
-                }
-            }
-        }
-
-        @Override
-        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
-            return mUsersOrdered;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            UserSwitcherController.UserRecord item = getItem(position);
-            return createUserDetailItemView(convertView, parent, item);
-        }
-
-        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
-            if (!(convertView instanceof KeyguardUserDetailItemView)
-                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
-                convertView = LayoutInflater.from(mContext).inflate(
-                        R.layout.keyguard_user_switcher_item, parent, false);
-            }
-            return (KeyguardUserDetailItemView) convertView;
-        }
-
-        UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
-                UserSwitcherController.UserRecord item) {
-            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
-            if (!item.isCurrent || item.isGuest) {
-                v.setOnClickListener(this);
-            } else {
-                v.setOnClickListener(null);
-                v.setClickable(false);
-            }
-
-            String name = getName(mContext, item);
-            if (item.picture == null) {
-                v.bind(name, getDrawable(mContext, item).mutate(), item.resolveId());
-            } else {
-                int avatarSize =
-                        (int) mContext.getResources().getDimension(R.dimen.kg_framed_avatar_size);
-                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
-                drawable.setColorFilter(
-                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
-                v.bind(name, drawable, item.info.id);
-            }
-            v.setActivated(item.isCurrent);
-            v.setDisabledByAdmin(item.isDisabledByAdmin);
-            v.setEnabled(item.isSwitchToEnabled);
-            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
-
-            if (item.isCurrent) {
-                mCurrentUserView = v;
-            }
-            v.setTag(item);
-            return v;
-        }
-
-        private static Drawable getDrawable(Context context,
-                UserSwitcherController.UserRecord item) {
-            Drawable drawable = getIconDrawable(context, item);
-            int iconColorRes;
-            if (item.isCurrent) {
-                iconColorRes = R.color.kg_user_switcher_selected_avatar_icon_color;
-            } else if (!item.isSwitchToEnabled) {
-                iconColorRes = R.color.GM2_grey_600;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            }
-            drawable.setTint(context.getResources().getColor(iconColorRes, context.getTheme()));
-
-            if (item.isCurrent) {
-                Drawable bg = context.getDrawable(R.drawable.bg_avatar_selected);
-                drawable = new LayerDrawable(new Drawable[]{bg, drawable});
-            }
-
-            return drawable;
-        }
-
-        @Override
-        public void onClick(View v) {
-            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
-            if (user.isCurrent && !user.isGuest) {
-                // Close the switcher if tapping the current user. Guest is excluded because
-                // tapping the guest user while it's current clears the session.
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            } else if (user.isSwitchToEnabled) {
-                if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
-                    if (mCurrentUserView != null) {
-                        mCurrentUserView.setActivated(false);
-                    }
-                    v.setActivated(true);
-                }
-                onUserListItemClicked(user);
-            }
-        }
-    }
-
-    public static class Container extends FrameLayout {
-
-        private KeyguardUserSwitcher mKeyguardUserSwitcher;
-
-        public Container(Context context, AttributeSet attrs) {
-            super(context, attrs);
-            setClipChildren(false);
-        }
-
-        public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-            mKeyguardUserSwitcher = keyguardUserSwitcher;
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent ev) {
-            // Hide switcher if it didn't handle the touch event (and let the event go through).
-            if (mKeyguardUserSwitcher != null && !mKeyguardUserSwitcher.isAnimating()) {
-                mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-            }
-            return false;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
new file mode 100644
index 0000000..b76e451
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
+import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.keyguard.KeyguardConstants;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardVisibilityHelper;
+import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.ViewController;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+@KeyguardUserSwitcherScope
+public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
+
+    private static final String TAG = "KeyguardUserSwitcherController";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final AnimationProperties ANIMATION_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+    private final Context mContext;
+    private final UserSwitcherController mUserSwitcherController;
+    private final ScreenLifecycle mScreenLifecycle;
+    private final KeyguardUserAdapter mAdapter;
+    private final KeyguardStateController mKeyguardStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private WeakReference<KeyguardUserSwitcherListener> mKeyguardUserSwitcherCallback;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+    private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+
+    // Child views of KeyguardUserSwitcherView
+    private KeyguardUserSwitcherListView mListView;
+    private LinearLayout mEndGuestButton;
+
+    // State info for the user switcher
+    private boolean mUserSwitcherOpen;
+    private int mCurrentUserId = UserHandle.USER_NULL;
+    private boolean mCurrentUserIsGuest;
+    private int mBarState;
+    private float mDarkAmount;
+
+    private final KeyguardUpdateMonitorCallback mInfoCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
+                    // Any time the keyguard is hidden, try to close the user switcher menu to
+                    // restore keyguard to the default state
+                    if (!showing) {
+                        closeSwitcherIfOpenAndNotSimple(false);
+                    }
+                }
+
+                @Override
+                public void onUserSwitching(int userId) {
+                    closeSwitcherIfOpenAndNotSimple(false);
+                }
+            };
+
+    private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+        @Override
+        public void onScreenTurnedOff() {
+            if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    };
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onStateChanged(int newState) {
+                    if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
+
+                    boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+                    boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+                    int oldState = mBarState;
+                    mBarState = newState;
+
+                    if (mStatusBarStateController.goingToFullShade()
+                            || mKeyguardStateController.isKeyguardFadingAway()) {
+                        closeSwitcherIfOpenAndNotSimple(true);
+                    }
+
+                    setKeyguardUserSwitcherVisibility(
+                            newState,
+                            keyguardFadingAway,
+                            goingToFullShade,
+                            oldState);
+                }
+
+                @Override
+                public void onDozeAmountChanged(float linearAmount, float amount) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
+                                linearAmount, amount));
+                    }
+                    setDarkAmount(amount);
+                }
+            };
+
+    @Inject
+    public KeyguardUserSwitcherController(
+            KeyguardUserSwitcherView keyguardUserSwitcherView,
+            Context context,
+            @Main Resources resources,
+            LayoutInflater layoutInflater,
+            ScreenLifecycle screenLifecycle,
+            UserSwitcherController userSwitcherController,
+            KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DozeParameters dozeParameters) {
+        super(keyguardUserSwitcherView);
+        if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
+        mContext = context;
+        mScreenLifecycle = screenLifecycle;
+        mUserSwitcherController = userSwitcherController;
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
+                mUserSwitcherController, this);
+        mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
+                keyguardStateController, dozeParameters);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+
+        if (DEBUG) Log.d(TAG, "onInit");
+
+        mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
+        mEndGuestButton = mView.findViewById(R.id.end_guest_button);
+
+        mEndGuestButton.setOnClickListener(v -> {
+            mUserSwitcherController.showExitGuestDialog(mCurrentUserId);
+        });
+
+        mView.setOnTouchListener((v, event) -> {
+            if (!isListAnimating()) {
+                // Hide switcher if it didn't handle the touch event (and block the event from
+                // going through).
+                return closeSwitcherIfOpenAndNotSimple(true);
+            }
+            return false;
+        });
+    }
+
+    @Override
+    protected void onViewAttached() {
+        if (DEBUG) Log.d(TAG, "onViewAttached");
+        mAdapter.registerDataSetObserver(mDataSetObserver);
+        mDataSetObserver.onChanged();
+        mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mScreenLifecycle.addObserver(mScreenObserver);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        if (DEBUG) Log.d(TAG, "onViewDetached");
+
+        // Detaching the view will always close the switcher
+        closeSwitcherIfOpenAndNotSimple(false);
+
+        mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+        mStatusBarStateController.removeCallback(mStatusBarStateListener);
+        mScreenLifecycle.removeObserver(mScreenObserver);
+    }
+
+    /**
+     * See:
+     *
+     * <ul>
+     *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
+     *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
+     * </ul>
+     *
+     * @return true if the user switcher should be open by default on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    public boolean isSimpleUserSwitcher() {
+        return mUserSwitcherController.isSimpleUserSwitcher();
+    }
+
+    /**
+     * @param animate if the transition should be animated
+     * @return true if the switcher state changed
+     */
+    public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
+        if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
+            setUserSwitcherOpened(false /* open */, animate);
+            return true;
+        }
+        return false;
+    }
+
+    public final DataSetObserver mDataSetObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            refreshUserList();
+        }
+    };
+
+    void refreshUserList() {
+        final int childCount = mListView.getChildCount();
+        final int adapterCount = mAdapter.getCount();
+        final int count = Math.max(childCount, adapterCount);
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
+                    adapterCount));
+        }
+
+        boolean foundCurrentUser = false;
+        for (int i = 0; i < count; i++) {
+            if (i < adapterCount) {
+                View oldView = null;
+                if (i < childCount) {
+                    oldView = mListView.getChildAt(i);
+                }
+                KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
+                        mAdapter.getView(i, oldView, mListView);
+                UserSwitcherController.UserRecord userTag =
+                        (UserSwitcherController.UserRecord) newView.getTag();
+                if (userTag.isCurrent) {
+                    if (i != 0) {
+                        Log.w(TAG, "Current user is not the first view in the list");
+                    }
+                    foundCurrentUser = true;
+                    mCurrentUserId = userTag.info.id;
+                    mCurrentUserIsGuest = userTag.isGuest;
+                    // Current user is always visible
+                    newView.updateVisibilities(true /* showItem */,
+                            mUserSwitcherOpen /* showTextName */, false /* animate */);
+                } else {
+                    // Views for non-current users are always expanded (e.g. they should the name
+                    // next to the user icon). However, they could be hidden entirely if the list
+                    // is closed.
+                    newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
+                            true /* showTextName */, false /* animate */);
+                }
+                newView.setDarkAmount(mDarkAmount);
+                if (oldView == null) {
+                    // We ran out of existing views. Add it at the end.
+                    mListView.addView(newView);
+                } else if (oldView != newView) {
+                    // We couldn't rebind the view. Replace it.
+                    mListView.replaceView(newView, i);
+                }
+            } else {
+                mListView.removeLastView();
+            }
+        }
+        if (!foundCurrentUser) {
+            Log.w(TAG, "Current user is not listed");
+            mCurrentUserId = UserHandle.USER_NULL;
+            mCurrentUserIsGuest = false;
+        }
+    }
+
+    /**
+     * Get the height of the keyguard user switcher view when closed.
+     */
+    public int getUserIconHeight() {
+        View firstChild = mListView.getChildAt(0);
+        return firstChild == null ? 0 : firstChild.getHeight();
+    }
+
+    /**
+     * Set the visibility of the keyguard user switcher view based on some new state.
+     */
+    public void setKeyguardUserSwitcherVisibility(
+            int statusBarState,
+            boolean keyguardFadingAway,
+            boolean goingToFullShade,
+            int oldStatusBarState) {
+        mKeyguardVisibilityHelper.setViewVisibility(
+                statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
+    }
+
+    /**
+     * Update position of the view with an optional animation
+     */
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
+                animate);
+        PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
+                ANIMATION_PROPERTIES, animate);
+    }
+
+    /**
+     * Set keyguard user switcher view alpha.
+     */
+    public void setAlpha(float alpha) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+            mView.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    private void setDarkAmount(float darkAmount) {
+        boolean isAwake = darkAmount != 0;
+        if (darkAmount == mDarkAmount) {
+            return;
+        }
+        mDarkAmount = darkAmount;
+        mListView.setDarkAmount(darkAmount);
+        mView.setVisibility(isAwake ? View.VISIBLE : View.GONE);
+        if (!isAwake) {
+            closeSwitcherIfOpenAndNotSimple(false);
+        }
+    }
+
+    private boolean isListAnimating() {
+        return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
+    }
+
+    /**
+     * Remove the callback if it exists.
+     */
+    public void removeCallback() {
+        if (DEBUG) Log.d(TAG, "removeCallback");
+        mKeyguardUserSwitcherCallback = null;
+    }
+
+    /**
+     * Register to receive notifications about keyguard user switcher state
+     * (see {@link KeyguardUserSwitcherListener}.
+     *
+     * Only one callback can be used at a time.
+     *
+     * @param callback The callback to register
+     */
+    public void setCallback(KeyguardUserSwitcherListener callback) {
+        if (DEBUG) Log.d(TAG, "setCallback");
+        mKeyguardUserSwitcherCallback = new WeakReference<>(callback);
+    }
+
+    /**
+     * If user switcher state changes, notifies all {@link KeyguardUserSwitcherListener}.
+     * Switcher state is updatd before animations finish.
+     *
+     * @param animate true to animate transition. The user switcher state (i.e.
+     *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
+     */
+    private void setUserSwitcherOpened(boolean open, boolean animate) {
+        boolean wasOpen = mUserSwitcherOpen;
+        if (DEBUG) {
+            Log.d(TAG, String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", wasOpen,
+                    open, animate));
+        }
+        mUserSwitcherOpen = open;
+        if (mUserSwitcherOpen != wasOpen) {
+            notifyUserSwitcherStateChanged();
+        }
+        updateVisibilities(animate);
+    }
+
+    private void updateVisibilities(boolean animate) {
+        if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
+        mEndGuestButton.animate().cancel();
+        if (mUserSwitcherOpen && mCurrentUserIsGuest) {
+            // Show the "End guest session" button
+            mEndGuestButton.setVisibility(View.VISIBLE);
+            if (animate) {
+                mEndGuestButton.setAlpha(0f);
+                mEndGuestButton.animate()
+                        .alpha(1f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_IN)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setClickable(true);
+                        });
+            } else {
+                mEndGuestButton.setClickable(true);
+                mEndGuestButton.setAlpha(1f);
+            }
+        } else {
+            // Hide the "End guest session" button. If it's already GONE, don't try to
+            // animate it or it will appear again for an instant.
+            mEndGuestButton.setClickable(false);
+            if (animate && mEndGuestButton.getVisibility() != View.GONE) {
+                mEndGuestButton.setVisibility(View.VISIBLE);
+                mEndGuestButton.setAlpha(1f);
+                mEndGuestButton.animate()
+                        .alpha(0f)
+                        .setDuration(360)
+                        .setInterpolator(Interpolators.ALPHA_OUT)
+                        .withEndAction(() -> {
+                            mEndGuestButton.setVisibility(View.GONE);
+                            mEndGuestButton.setAlpha(1f);
+                        });
+            } else {
+                mEndGuestButton.setVisibility(View.GONE);
+                mEndGuestButton.setAlpha(1f);
+            }
+        }
+
+        mListView.updateVisibilities(mUserSwitcherOpen, animate);
+    }
+
+    private boolean isUserSwitcherOpen() {
+        return mUserSwitcherOpen;
+    }
+
+    private void notifyUserSwitcherStateChanged() {
+        if (DEBUG) {
+            Log.d(TAG, String.format("notifyUserSwitcherStateChanged: mUserSwitcherOpen=%b",
+                    mUserSwitcherOpen));
+        }
+        if (mKeyguardUserSwitcherCallback != null) {
+            KeyguardUserSwitcherListener cb = mKeyguardUserSwitcherCallback.get();
+            if (cb != null) {
+                cb.onKeyguardUserSwitcherChanged(mUserSwitcherOpen);
+            }
+        }
+    }
+
+    /**
+     * Callback for keyguard user switcher state information
+     */
+    public interface KeyguardUserSwitcherListener {
+
+        /**
+         * Called when the keyguard enters or leaves user switcher mode. This will be called
+         * before the animations are finished.
+         *
+         * @param open if true, keyguard is showing the user switcher or transitioning from/to user
+         *             switcher mode.
+         */
+        void onKeyguardUserSwitcherChanged(boolean open);
+    }
+
+    static class KeyguardUserAdapter extends
+            UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
+
+        private final Context mContext;
+        private final Resources mResources;
+        private final LayoutInflater mLayoutInflater;
+        private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
+        private View mCurrentUserView;
+        // List of users where the first entry is always the current user
+        private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
+
+        KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
+                UserSwitcherController controller,
+                KeyguardUserSwitcherController keyguardUserSwitcherController) {
+            super(controller);
+            mContext = context;
+            mResources = resources;
+            mLayoutInflater = layoutInflater;
+            mKeyguardUserSwitcherController = keyguardUserSwitcherController;
+        }
+
+        @Override
+        public void notifyDataSetChanged() {
+            // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
+            // data set
+            refreshUserOrder();
+            super.notifyDataSetChanged();
+        }
+
+        void refreshUserOrder() {
+            ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
+            mUsersOrdered = new ArrayList<>(users.size());
+            for (int i = 0; i < users.size(); i++) {
+                UserSwitcherController.UserRecord record = users.get(i);
+                if (record.isCurrent) {
+                    mUsersOrdered.add(0, record);
+                } else {
+                    mUsersOrdered.add(record);
+                }
+            }
+        }
+
+        @Override
+        protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
+            return mUsersOrdered;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            UserSwitcherController.UserRecord item = getItem(position);
+            return createUserDetailItemView(convertView, parent, item);
+        }
+
+        @Override
+        public String getName(Context context, UserSwitcherController.UserRecord item) {
+            if (item.isGuest) {
+                return context.getString(com.android.settingslib.R.string.guest_nickname);
+            } else {
+                return super.getName(context, item);
+            }
+        }
+
+        KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
+            if (!(convertView instanceof KeyguardUserDetailItemView)
+                    || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
+                convertView = mLayoutInflater.inflate(
+                        R.layout.keyguard_user_switcher_item, parent, false);
+            }
+            return (KeyguardUserDetailItemView) convertView;
+        }
+
+        KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
+                UserSwitcherController.UserRecord item) {
+            KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
+            v.setOnClickListener(this);
+
+            String name = getName(mContext, item);
+            if (item.picture == null) {
+                v.bind(name, getDrawable(item).mutate(), item.resolveId());
+            } else {
+                int avatarSize =
+                        (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+                Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
+                drawable.setColorFilter(
+                        item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
+                v.bind(name, drawable, item.info.id);
+            }
+            v.setActivated(item.isCurrent);
+            v.setDisabledByAdmin(item.isDisabledByAdmin);
+            v.setEnabled(item.isSwitchToEnabled);
+            v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
+
+            if (item.isCurrent) {
+                mCurrentUserView = v;
+            }
+            v.setTag(item);
+            return v;
+        }
+
+        private Drawable getDrawable(UserSwitcherController.UserRecord item) {
+            Drawable drawable;
+            if (item.isCurrent && item.isGuest) {
+                drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+            } else {
+                drawable = getIconDrawable(mContext, item);
+            }
+
+            int iconColorRes;
+            if (item.isSwitchToEnabled) {
+                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+            } else {
+                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+            }
+            drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
+
+            Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
+            drawable = new LayerDrawable(new Drawable[]{bg, drawable});
+            return drawable;
+        }
+
+        @Override
+        public void onClick(View v) {
+            UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
+
+            if (mKeyguardUserSwitcherController.isListAnimating()) {
+                return;
+            }
+
+            if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
+                if (user.isCurrent) {
+                    // Close the switcher if tapping the current user
+                    mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                            false /* open */, true /* animate */);
+                } else if (user.isSwitchToEnabled) {
+                    if (!user.isAddUser && !user.isRestricted && !user.isDisabledByAdmin) {
+                        if (mCurrentUserView != null) {
+                            mCurrentUserView.setActivated(false);
+                        }
+                        v.setActivated(true);
+                    }
+                    onUserListItemClicked(user);
+                }
+            } else {
+                // If switcher is closed, tapping anywhere in the view will open it
+                mKeyguardUserSwitcherController.setUserSwitcherOpened(
+                        true /* open */, true /* animate */);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
new file mode 100644
index 0000000..7c82c11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.keyguard.AlphaOptimizedLinearLayout;
+import com.android.keyguard.KeyguardConstants;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Interpolators;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
+
+    private static final String TAG = "KeyguardUserSwitcherListView";
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private static final int ANIMATION_DURATION_OPENING = 360;
+    private static final int ANIMATION_DURATION_CLOSING = 240;
+
+    private boolean mAnimating;
+    private final AppearAnimationUtils mAppearAnimationUtils;
+    private final DisappearAnimationUtils mDisappearAnimationUtils;
+
+    public KeyguardUserSwitcherListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setClipChildren(false);
+        mAppearAnimationUtils = new AppearAnimationUtils(context, ANIMATION_DURATION_OPENING,
+                -0.5f, 0.5f, Interpolators.FAST_OUT_SLOW_IN);
+        mDisappearAnimationUtils = new DisappearAnimationUtils(context, ANIMATION_DURATION_CLOSING,
+                0.5f, 0.5f, Interpolators.FAST_OUT_LINEAR_IN);
+    }
+
+    /**
+     * Set the amount (ratio) that the device has transitioned to doze.
+     *
+     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+     */
+    void setDarkAmount(float darkAmount) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            if (v instanceof KeyguardUserDetailItemView) {
+                ((KeyguardUserDetailItemView) v).setDarkAmount(darkAmount);
+            }
+        }
+    }
+
+    boolean isAnimating() {
+        return mAnimating;
+    }
+
+    /**
+     * Update visibilities of this view and child views for when the user list is open or closed.
+     * If closed, this hides everything but the first item (which is always the current user).
+     */
+    void updateVisibilities(boolean open, boolean animate) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("updateVisibilities: open=%b animate=%b childCount=%d",
+                    open, animate, getChildCount()));
+        }
+
+        mAnimating = false;
+
+        int userListCount = getChildCount();
+        if (userListCount > 0) {
+            // The first child is always the current user.
+            KeyguardUserDetailItemView currentUserView = ((KeyguardUserDetailItemView) getChildAt(
+                    0));
+            currentUserView.updateVisibilities(true /* showItem */, open /* showTextName */,
+                    animate);
+            currentUserView.setClickable(true);
+            currentUserView.clearAnimation();
+        }
+
+        if (userListCount <= 1) {
+            return;
+        }
+
+        if (animate) {
+            // Create an array of all the remaining users (that aren't the current user).
+            KeyguardUserDetailItemView[] otherUserViews =
+                    new KeyguardUserDetailItemView[userListCount - 1];
+            for (int i = 1, n = 0; i < userListCount; i++, n++) {
+                otherUserViews[n] = (KeyguardUserDetailItemView) getChildAt(i);
+
+                // Update clickable state immediately so that the menu feels more responsive
+                otherUserViews[n].setClickable(open);
+
+                // Before running the animation, ensure visibility is set correctly
+                otherUserViews[n].updateVisibilities(
+                        true /* showItem */, true /* showTextName */, false /* animate */);
+                otherUserViews[n].clearAnimation();
+            }
+
+            setClipChildren(false);
+            setClipToPadding(false);
+
+            mAnimating = true;
+
+            final int nonCurrentUserCount = otherUserViews.length;
+            if (open) {
+                mAppearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    mAnimating = false;
+                });
+            } else {
+                mDisappearAnimationUtils.startAnimation(otherUserViews, () -> {
+                    setClipChildren(true);
+                    setClipToPadding(true);
+                    for (int i = 0; i < nonCurrentUserCount; i++) {
+                        otherUserViews[i].updateVisibilities(
+                                false /* showItem */, true /* showTextName */, false /* animate */);
+                    }
+                    mAnimating = false;
+                });
+            }
+        } else {
+            for (int i = 1; i < userListCount; i++) {
+                KeyguardUserDetailItemView nonCurrentUserView =
+                        ((KeyguardUserDetailItemView) getChildAt(i));
+                nonCurrentUserView.clearAnimation();
+                nonCurrentUserView.updateVisibilities(
+                        open /* showItem */, true /* showTextName */, false /* animate */);
+                nonCurrentUserView.setClickable(open);
+            }
+        }
+    }
+
+    /**
+     * Replaces the view at the specified position in the group.
+     *
+     * @param index the position in the group of the view to remove
+     */
+    void replaceView(KeyguardUserDetailItemView newView, int index) {
+        removeViewAt(index);
+        addView(newView, index);
+    }
+
+    /**
+     * Removes the last view in the group.
+     */
+    void removeLastView() {
+        int lastIndex = getChildCount() - 1;
+        removeViewAt(lastIndex);
+    }
+}
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
similarity index 61%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
index 14d57bf..3f0e23f 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherView.java
@@ -14,6 +14,18 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.statusbar.policy;
 
-parcelable ExternalTimeSuggestion;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * The container for the user switcher on Keyguard.
+ */
+public class KeyguardUserSwitcherView extends FrameLayout {
+
+    public KeyguardUserSwitcherView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 0fe338e..1ab7652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings.Global;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthCdma;
 import android.telephony.ServiceState;
@@ -34,12 +35,18 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager.RegistrationCallback;
 import android.text.Html;
 import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.AccessibilityContentDescriptions;
 import com.android.settingslib.SignalIcon.MobileIconGroup;
 import com.android.settingslib.SignalIcon.MobileState;
 import com.android.settingslib.Utils;
@@ -65,13 +72,19 @@
  */
 public class MobileSignalController extends SignalController<MobileState, MobileIconGroup> {
     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-
+    private static final int STATUS_HISTORY_SIZE = 64;
+    private static final int IMS_TYPE_WWAN = 1;
+    private static final int IMS_TYPE_WLAN = 2;
+    private static final int IMS_TYPE_WLAN_CROSS_SIM = 3;
     private final TelephonyManager mPhone;
+    private final ImsMmTelManager mImsMmTelManager;
     private final SubscriptionDefaults mDefaults;
     private final String mNetworkNameDefault;
     private final String mNetworkNameSeparator;
     private final ContentObserver mObserver;
     private final boolean mProviderModel;
+    private final Handler mReceiverHandler;
+    private int mImsType = IMS_TYPE_WWAN;
     // Save entire info for logging, we only use the id.
     final SubscriptionInfo mSubscriptionInfo;
     // @VisibleForDemoMode
@@ -86,16 +99,21 @@
                     TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
     private ServiceState mServiceState;
     private SignalStrength mSignalStrength;
+    private int mLastLevel;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
     @VisibleForTesting
     boolean mInflateSignalStrengths = false;
     private MobileStatusTracker.Callback mCallback;
+    private RegistrationCallback mRegistrationCallback;
+    private int mLastWwanLevel;
+    private int mLastWlanLevel;
+    private int mLastWlanCrossSimLevel;
     @VisibleForTesting
     MobileStatusTracker mMobileStatusTracker;
 
-    // Save the previous HISTORY_SIZE states for logging.
-    private final String[] mMobileStatusHistory = new String[HISTORY_SIZE];
+    // Save the previous STATUS_HISTORY_SIZE states for logging.
+    private final String[] mMobileStatusHistory = new String[STATUS_HISTORY_SIZE];
     // Where to copy the next state into.
     private int mMobileStatusHistoryIndex;
 
@@ -116,6 +134,7 @@
                 .toString();
         mNetworkNameDefault = getTextIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default).toString();
+        mReceiverHandler = new Handler(receiverLooper);
 
         mNetworkToIconLookup = mapIconSets(mConfig);
         mDefaultIcons = getDefaultIcons(mConfig);
@@ -133,6 +152,8 @@
             }
         };
         mCallback = new MobileStatusTracker.Callback() {
+            private String mLastStatus;
+
             @Override
             public void onMobileStatusChanged(boolean updateTelephony,
                     MobileStatus mobileStatus) {
@@ -141,11 +162,15 @@
                             + " updateTelephony=" + updateTelephony
                             + " mobileStatus=" + mobileStatus.toString());
                 }
-                String status = new StringBuilder()
-                        .append(SSDF.format(System.currentTimeMillis())).append(",")
-                        .append(mobileStatus.toString())
-                        .toString();
-                recordLastMobileStatus(status);
+                String currentStatus = mobileStatus.toString();
+                if (!currentStatus.equals(mLastStatus)) {
+                    mLastStatus = currentStatus;
+                    String status = new StringBuilder()
+                            .append(SSDF.format(System.currentTimeMillis())).append(",")
+                            .append(currentStatus)
+                            .toString();
+                    recordLastMobileStatus(status);
+                }
                 updateMobileStatus(mobileStatus);
                 if (updateTelephony) {
                     updateTelephony();
@@ -154,6 +179,53 @@
                 }
             }
         };
+
+        mRegistrationCallback = new RegistrationCallback() {
+            @Override
+            public void onRegistered(ImsRegistrationAttributes attributes) {
+                Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
+                int imsTransportType = attributes.getTransportType();
+                int registrationAttributes = attributes.getAttributeFlags();
+                if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                    mImsType = IMS_TYPE_WWAN;
+                    IconState statusIcon = new IconState(
+                            true,
+                            getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                            getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                    notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                    if (registrationAttributes == 0) {
+                        mImsType = IMS_TYPE_WLAN;
+                        IconState statusIcon = new IconState(
+                                true,
+                                getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+                                getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+                        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                    } else if (registrationAttributes
+                            == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
+                        mImsType = IMS_TYPE_WLAN_CROSS_SIM;
+                        IconState statusIcon = new IconState(
+                                true,
+                                getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+                                getCallStrengthDescription(
+                                        mLastWlanCrossSimLevel, /* isWifi= */false));
+                        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+                    }
+                }
+            }
+
+            @Override
+            public void onUnregistered(ImsReasonInfo info) {
+                Log.d(mTag, "onDeregistered: " + "info=" + info);
+                mImsType = IMS_TYPE_WWAN;
+                IconState statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+            }
+        };
+        mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
         mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
                 info, mDefaults, mCallback);
         mProviderModel = FeatureFlagUtils.isEnabled(
@@ -202,14 +274,41 @@
         mContext.getContentResolver().registerContentObserver(Global.getUriFor(
                 Global.MOBILE_DATA + mSubscriptionInfo.getSubscriptionId()),
                 true, mObserver);
+        if (mProviderModel) {
+            mReceiverHandler.post(mTryRegisterIms);
+        }
     }
 
+    // There is no listener to monitor whether the IMS service is ready, so we have to retry the
+    // IMS registration.
+    private final Runnable mTryRegisterIms = new Runnable() {
+        private static final int MAX_RETRY = 12;
+        private int mRetryCount;
+
+        @Override
+        public void run() {
+            try {
+                mRetryCount++;
+                mImsMmTelManager.registerImsRegistrationCallback(
+                        mReceiverHandler::post, mRegistrationCallback);
+                Log.d(mTag, "registerImsRegistrationCallback succeeded");
+            } catch (RuntimeException | ImsException e) {
+                if (mRetryCount < MAX_RETRY) {
+                    Log.e(mTag, mRetryCount + " registerImsRegistrationCallback failed", e);
+                    // Wait for 5 seconds to retry
+                    mReceiverHandler.postDelayed(mTryRegisterIms, 5000);
+                }
+            }
+        }
+    };
+
     /**
      * Stop listening for phone state changes.
      */
     public void unregisterListener() {
         mMobileStatusTracker.setListening(false);
         mContext.getContentResolver().unregisterContentObserver(mObserver);
+        mImsMmTelManager.unregisterImsRegistrationCallback(mRegistrationCallback);
     }
 
     private void updateInflateSignalStrength() {
@@ -452,9 +551,9 @@
     /**
      * Extracts the CellSignalStrengthCdma from SignalStrength then returns the level
      */
-    private final int getCdmaLevel() {
+    private int getCdmaLevel(SignalStrength signalStrength) {
         List<CellSignalStrengthCdma> signalStrengthCdma =
-            mSignalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
+                signalStrength.getCellSignalStrengths(CellSignalStrengthCdma.class);
         if (!signalStrengthCdma.isEmpty()) {
             return signalStrengthCdma.get(0).getLevel();
         }
@@ -467,6 +566,7 @@
         mCurrentState.dataSim = mobileStatus.dataSim;
         mCurrentState.carrierNetworkChangeMode = mobileStatus.carrierNetworkChangeMode;
         mDataState = mobileStatus.dataState;
+        notifyMobileLevelChangeIfNecessary(mobileStatus.signalStrength);
         mSignalStrength = mobileStatus.signalStrength;
         mTelephonyDisplayInfo = mobileStatus.telephonyDisplayInfo;
         int lastVoiceState = mServiceState != null ? mServiceState.getState() : -1;
@@ -481,9 +581,117 @@
                 && (lastVoiceState == -1
                         || (lastVoiceState == ServiceState.STATE_IN_SERVICE
                                 || currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
-            notifyNoCallingStatusChange(
-                    currentVoiceState != ServiceState.STATE_IN_SERVICE,
-                    mSubscriptionInfo.getSubscriptionId());
+            boolean isNoCalling = currentVoiceState != ServiceState.STATE_IN_SERVICE;
+            IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+                    getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+            notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+        }
+    }
+
+    private int getCallStrengthIcon(int level, boolean isWifi) {
+        return isWifi ? TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[level]
+                : TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[level];
+    }
+
+    private String getCallStrengthDescription(int level, boolean isWifi) {
+        return isWifi
+                ? getTextIfExists(AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[level])
+                        .toString()
+                : getTextIfExists(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[level])
+                        .toString();
+    }
+
+    void refreshCallIndicator(SignalCallback callback) {
+        boolean isNoCalling = mServiceState != null
+                && mServiceState.getState() != ServiceState.STATE_IN_SERVICE;
+        IconState statusIcon = new IconState(isNoCalling, R.drawable.ic_qs_no_calling_sms,
+                getTextIfExists(AccessibilityContentDescriptions.NO_CALLING).toString());
+        callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+
+        switch (mImsType) {
+            case IMS_TYPE_WWAN:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+                break;
+            case IMS_TYPE_WLAN:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+                        getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+                break;
+            case IMS_TYPE_WLAN_CROSS_SIM:
+                statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+                        getCallStrengthDescription(mLastWlanCrossSimLevel, /* isWifi= */false));
+        }
+        callback.setCallIndicator(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyWifiLevelChange(int level) {
+        if (!mProviderModel) {
+            return;
+        }
+        Log.d("mTag", "notifyWifiLevelChange " + mImsType);
+        mLastWlanLevel = level;
+        if (mImsType != IMS_TYPE_WLAN) {
+            return;
+        }
+        IconState statusIcon = new IconState(
+                true,
+                getCallStrengthIcon(level, /* isWifi= */true),
+                getCallStrengthDescription(level, /* isWifi= */true));
+        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyDefaultMobileLevelChange(int level) {
+        if (!mProviderModel) {
+            return;
+        }
+        Log.d("mTag", "notifyDefaultMobileLevelChange " + mImsType);
+        mLastWlanCrossSimLevel = level;
+        if (mImsType != IMS_TYPE_WLAN_CROSS_SIM) {
+            return;
+        }
+        IconState statusIcon = new IconState(
+                true,
+                getCallStrengthIcon(level, /* isWifi= */false),
+                getCallStrengthDescription(level, /* isWifi= */false));
+        notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+    }
+
+    void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
+        if (!mProviderModel) {
+            return;
+        }
+        int newLevel = getSignalLevel(signalStrength);
+        if (newLevel != mLastLevel) {
+            mLastLevel = newLevel;
+            Log.d("mTag", "notifyMobileLevelChangeIfNecessary " + mImsType);
+            mLastWwanLevel = newLevel;
+            if (mImsType == IMS_TYPE_WWAN) {
+                IconState statusIcon = new IconState(
+                        true,
+                        getCallStrengthIcon(newLevel, /* isWifi= */false),
+                        getCallStrengthDescription(newLevel, /* isWifi= */false));
+                notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+            }
+            if (mCurrentState.dataSim) {
+                mNetworkController.notifyDefaultMobileLevelChange(newLevel);
+            }
+        }
+    }
+
+    int getSignalLevel(SignalStrength signalStrength) {
+        if (signalStrength == null) {
+            return 0;
+        }
+        if (!signalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
+            return getCdmaLevel(signalStrength);
+        } else {
+            return signalStrength.getLevel();
         }
     }
 
@@ -501,11 +709,7 @@
         checkDefaultData();
         mCurrentState.connected = Utils.isInService(mServiceState) && mSignalStrength != null;
         if (mCurrentState.connected) {
-            if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
-                mCurrentState.level = getCdmaLevel();
-            } else {
-                mCurrentState.level = mSignalStrength.getLevel();
-            }
+            mCurrentState.level = getSignalLevel(mSignalStrength);
         }
 
         String iconKey = getIconKey(mTelephonyDisplayInfo);
@@ -577,7 +781,13 @@
     }
 
     private void recordLastMobileStatus(String mobileStatus) {
-        mMobileStatusHistory[mMobileStatusHistoryIndex++ & (HISTORY_SIZE - 1)] = mobileStatus;
+        mMobileStatusHistory[mMobileStatusHistoryIndex] = mobileStatus;
+        mMobileStatusHistoryIndex = (mMobileStatusHistoryIndex + 1) % STATUS_HISTORY_SIZE;
+    }
+
+    @VisibleForTesting
+    void setImsType(int imsType) {
+        mImsType = imsType;
     }
 
     @Override
@@ -592,15 +802,17 @@
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
         pw.println("  MobileStatusHistory");
         int size = 0;
-        for (int i = 0; i < HISTORY_SIZE; i++) {
-            if (mMobileStatusHistory[i] != null) size++;
+        for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
+            if (mMobileStatusHistory[i] != null) {
+                size++;
+            }
         }
         // Print out the previous states in ordered number.
-        for (int i = mMobileStatusHistoryIndex + HISTORY_SIZE - 1;
-                i >= mMobileStatusHistoryIndex + HISTORY_SIZE - size; i--) {
+        for (int i = mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - 1;
+                i >= mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - size; i--) {
             pw.println("  Previous MobileStatus("
-                    + (mMobileStatusHistoryIndex + HISTORY_SIZE - i) + "): "
-                    + mMobileStatusHistory[i & (HISTORY_SIZE - 1)]);
+                    + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
+                    + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index e60d5c5..0a9fead 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -53,7 +53,7 @@
 
         /**
          * Callback for listeners to be able to update the state of any UI tracking connectivity
-         *  @param statusIcon the icon that should be shown in the status bar
+         * @param statusIcon the icon that should be shown in the status bar
          * @param qsIcon the icon to show in Quick Settings
          * @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar
          * @param qsType similar to above, the resId of the data type icon to show in Quick Settings
@@ -95,11 +95,11 @@
                 boolean noNetworksAvailable) {}
 
         /**
-         * Callback for listeners to be able to update the no calling & SMS status
-         * @param noCalling whether the calling and SMS is not working.
+         * Callback for listeners to be able to update the call indicator
+         * @param statusIcon the icon for the call indicator
          * @param subId subscription ID for which to update the UI
          */
-        default void setNoCallingStatus(boolean noCalling, int subId) {}
+        default void setCallIndicator(IconState statusIcon, int subId) {}
     }
 
     public interface EmergencyListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 80c7811..9f92142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -554,6 +554,20 @@
         return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
     }
 
+    void notifyWifiLevelChange(int level) {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.notifyWifiLevelChange(level);
+        }
+    }
+
+    void notifyDefaultMobileLevelChange(int level) {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.notifyDefaultMobileLevelChange(level);
+        }
+    }
+
     private void notifyControllersMobileDataChanged() {
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
@@ -623,6 +637,9 @@
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
             mobileSignalController.notifyListeners(cb);
+            if (mProviderModel) {
+                mobileSignalController.refreshCallIndicator(cb);
+            }
         }
         mCallbackHandler.setListening(cb, true);
     }
@@ -1272,7 +1289,8 @@
     }
 
     private void recordLastNetworkCallback(String callback) {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)] = callback;
+        mHistory[mHistoryIndex] = callback;
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4f4a504..738cab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -31,12 +31,11 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.IConnectivityManager;
 import android.net.Network;
 import android.net.NetworkRequest;
+import android.net.VpnManager;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.KeyChain;
@@ -84,7 +83,7 @@
 
     private final Context mContext;
     private final ConnectivityManager mConnectivityManager;
-    private final IConnectivityManager mConnectivityManagerService;
+    private final VpnManager mVpnManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
@@ -116,8 +115,7 @@
                 context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mConnectivityManager = (ConnectivityManager)
                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
-                ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+        mVpnManager = context.getSystemService(VpnManager.class);
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mBgExecutor = bgExecutor;
@@ -399,25 +397,19 @@
     private void updateState() {
         // Find all users with an active VPN
         SparseArray<VpnConfig> vpns = new SparseArray<>();
-        try {
-            for (UserInfo user : mUserManager.getUsers()) {
-                VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
-                if (cfg == null) {
+        for (UserInfo user : mUserManager.getUsers()) {
+            VpnConfig cfg = mVpnManager.getVpnConfig(user.id);
+            if (cfg == null) {
+                continue;
+            } else if (cfg.legacy) {
+                // Legacy VPNs should do nothing if the network is disconnected. Third-party
+                // VPN warnings need to continue as traffic can still go to the app.
+                LegacyVpnInfo legacyVpn = mVpnManager.getLegacyVpnInfo(user.id);
+                if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
                     continue;
-                } else if (cfg.legacy) {
-                    // Legacy VPNs should do nothing if the network is disconnected. Third-party
-                    // VPN warnings need to continue as traffic can still go to the app.
-                    LegacyVpnInfo legacyVpn = mConnectivityManagerService.getLegacyVpnInfo(user.id);
-                    if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
-                        continue;
-                    }
                 }
-                vpns.put(user.id, cfg);
             }
-        } catch (RemoteException rme) {
-            // Roll back to previous state
-            Log.e(TAG, "Unable to list active VPNs", rme);
-            return;
+            vpns.put(user.id, cfg);
         }
         mCurrentVpns = vpns;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 554145e..4b6722c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -23,6 +23,7 @@
 
 import com.android.settingslib.SignalIcon.IconGroup;
 import com.android.settingslib.SignalIcon.State;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.io.PrintWriter;
@@ -167,8 +168,8 @@
         }
     }
 
-    protected final void notifyNoCallingStatusChange(boolean noCalling, int subId) {
-        mCallbackHandler.setNoCallingStatus(noCalling, subId);
+    protected final void notifyCallStateChange(IconState statusIcon, int subId) {
+        mCallbackHandler.setCallIndicator(statusIcon, subId);
     }
 
     /**
@@ -187,7 +188,8 @@
      * and last value of any state data.
      */
     protected void recordLastState() {
-        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
+        mHistory[mHistoryIndex].copyFrom(mLastState);
+        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0552396..d4029e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -113,12 +113,15 @@
     private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
     private boolean mResumeUserOnGuestLogout = true;
     private boolean mSimpleUserSwitcher;
-    private boolean mAddUsersWhenLocked;
+    // When false, there won't be any visual affordance to add a new user from the keyguard even if
+    // the user is unlocked
+    private boolean mAddUsersFromLockScreen;
     private boolean mPauseRefreshUsers;
     private int mSecondaryUser = UserHandle.USER_NULL;
     private Intent mSecondaryUserServiceIntent;
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
     private final UiEventLogger mUiEventLogger;
+    public final DetailAdapter mUserDetailAdapter;
 
     @Inject
     public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@@ -129,6 +132,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mActivityTaskManager = activityTaskManager;
         mUiEventLogger = uiEventLogger;
+        mUserDetailAdapter = new UserDetailAdapter(this, mContext, mUiEventLogger);
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
         }
@@ -204,7 +208,7 @@
         }
         mForcePictureLoadForUserId.clear();
 
-        final boolean addUsersWhenLocked = mAddUsersWhenLocked;
+        final boolean addUsersWhenLocked = mAddUsersFromLockScreen;
         new AsyncTask<SparseArray<Bitmap>, Void, ArrayList<UserRecord>>() {
             @SuppressWarnings("unchecked")
             @Override
@@ -421,7 +425,7 @@
         }
     }
 
-    private void showExitGuestDialog(int id) {
+    protected void showExitGuestDialog(int id) {
         int newId = UserHandle.USER_SYSTEM;
         if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
             UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -554,7 +558,7 @@
     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
         public void onChange(boolean selfChange) {
             mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-            mAddUsersWhenLocked = Settings.Global.getInt(mContext.getContentResolver(),
+            mAddUsersFromLockScreen = Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
             refreshUsers(UserHandle.USER_NULL);
         };
@@ -610,43 +614,26 @@
         }
 
         public int getUserCount() {
-            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
-                    && mKeyguardStateController.isMethodSecure()
-                    && !mKeyguardStateController.canDismissLockScreen();
-            if (!secureKeyguardShowing) {
-                return getUsers().size();
-            }
-            // The lock screen is secure and showing. Filter out restricted records.
-            final int userSize = getUsers().size();
-            int count = 0;
-            for (int i = 0; i < userSize; i++) {
-                if (getUsers().get(i).isGuest) continue;
-                if (getUsers().get(i).isRestricted) {
-                    break;
-                } else {
-                    count++;
-                }
-            }
-            return count;
+            return countUsers(false);
         }
 
         @Override
         public int getCount() {
-            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
-                    && mKeyguardStateController.isMethodSecure()
-                    && !mKeyguardStateController.canDismissLockScreen();
-            if (!secureKeyguardShowing) {
-                return getUsers().size();
-            }
-            // The lock screen is secure and showing. Filter out restricted records.
+            return countUsers(true);
+        }
+
+        private int countUsers(boolean includeGuest) {
+            boolean keyguardShowing = mKeyguardStateController.isShowing();
             final int userSize = getUsers().size();
             int count = 0;
             for (int i = 0; i < userSize; i++) {
-                if (getUsers().get(i).isRestricted) {
-                    break;
-                } else {
-                    count++;
+                if (getUsers().get(i).isGuest && !includeGuest) {
+                    continue;
                 }
+                if (getUsers().get(i).isRestricted && keyguardShowing) {
+                    break;
+                }
+                count++;
             }
             return count;
         }
@@ -695,11 +682,7 @@
             if (item.isAddUser) {
                 iconRes = R.drawable.ic_add_circle;
             } else if (item.isGuest) {
-                if (item.isCurrent) {
-                    iconRes = R.drawable.ic_exit_to_app;
-                } else {
-                    iconRes = R.drawable.ic_avatar_guest_user;
-                }
+                iconRes = R.drawable.ic_avatar_guest_user;
             } else {
                 iconRes = R.drawable.ic_avatar_user;
             }
@@ -800,9 +783,20 @@
         }
     }
 
-    public final DetailAdapter userDetailAdapter = new DetailAdapter() {
+    public static class UserDetailAdapter implements DetailAdapter {
         private final Intent USER_SETTINGS_INTENT = new Intent(Settings.ACTION_USER_SETTINGS);
 
+        private final UserSwitcherController mUserSwitcherController;
+        private final Context mContext;
+        private final UiEventLogger mUiEventLogger;
+
+        UserDetailAdapter(UserSwitcherController userSwitcherController, Context context,
+                UiEventLogger uiEventLogger) {
+            mUserSwitcherController = userSwitcherController;
+            mContext = context;
+            mUiEventLogger = uiEventLogger;
+        }
+
         @Override
         public CharSequence getTitle() {
             return mContext.getString(R.string.quick_settings_user_title);
@@ -813,7 +807,7 @@
             UserDetailView v;
             if (!(convertView instanceof UserDetailView)) {
                 v = UserDetailView.inflate(context, parent, false);
-                v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger);
+                v.createAndSetAdapter(mUserSwitcherController, mUiEventLogger);
             } else {
                 v = (UserDetailView) convertView;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 1fd2ccb..16998d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
+import java.io.PrintWriter;
 import java.util.Objects;
 
 public class WifiSignalController extends
@@ -202,6 +203,7 @@
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
+        notifyWifiLevelChangeIfNecessary(mWifiTracker.level);
         mCurrentState.level = mWifiTracker.level;
         mCurrentState.statusLabel = mWifiTracker.statusLabel;
         mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged;
@@ -211,6 +213,12 @@
                         : mUnmergedWifiIconGroup;
     }
 
+    void notifyWifiLevelChangeIfNecessary(int level) {
+        if (level != mCurrentState.level) {
+            mNetworkController.notifyWifiLevelChange(level);
+        }
+    }
+
     boolean isCarrierMergedWifi(int subId) {
         return mCurrentState.isDefault
                 && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
@@ -225,6 +233,12 @@
         notifyListenersIfNecessary();
     }
 
+    @Override
+    public void dump(PrintWriter pw) {
+        super.dump(pw);
+        mWifiTracker.dump(pw);
+    }
+
     /**
      * Handler to receive the data activity on wifi.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e357577..9e78a66 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,12 +15,15 @@
  */
 package com.android.systemui.theme;
 
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
-import android.os.SystemProperties;
+import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -50,13 +53,6 @@
 public class ThemeOverlayApplier implements Dumpable {
     private static final String TAG = "ThemeOverlayApplier";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean MONET_ENABLED = SystemProperties
-            .getBoolean("persist.sysui.monet", false);
-
-    @VisibleForTesting
-    static final String MONET_ACCENT_COLOR_PACKAGE = "com.android.theme.accentcolor.color";
-    @VisibleForTesting
-    static final String MONET_SYSTEM_PALETTE_PACKAGE = "com.android.theme.systemcolors.color";
 
     @VisibleForTesting
     static final String ANDROID_PACKAGE = "android";
@@ -65,10 +61,8 @@
     @VisibleForTesting
     static final String SYSUI_PACKAGE = "com.android.systemui";
 
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_ACCENT_COLOR =
             "android.theme.customization.accent_color";
-    @VisibleForTesting
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
             "android.theme.customization.system_palette";
     @VisibleForTesting
@@ -117,16 +111,6 @@
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI);
 
-    /**
-     * List of main colors of Monet themes. These are extracted from overlays installed
-     * on the system.
-     */
-    private final ArrayList<Integer> mMainSystemColors = new ArrayList<>();
-    /**
-     * Same as above, but providing accent colors instead of a system palette.
-     */
-    private final ArrayList<Integer> mAccentColors = new ArrayList<>();
-
     /* Allowed overlay categories for each target package. */
     private final Map<String, Set<String>> mTargetPackageToCategories = new ArrayMap<>();
     /* Target package for each overlay category. */
@@ -162,64 +146,17 @@
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_LAUNCHER, mLauncherPackage);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_THEME_PICKER, mThemePickerPackage);
 
-        collectMonetSystemOverlays();
         dumpManager.registerDumpable(TAG, this);
     }
 
     /**
-     * List of accent colors available as Monet overlays.
-     */
-    List<Integer> getAvailableAccentColors() {
-        return mAccentColors;
-    }
-
-    /**
-     * List of main system colors available as Monet overlays.
-     */
-    List<Integer> getAvailableSystemColors() {
-        return mMainSystemColors;
-    }
-
-    private void collectMonetSystemOverlays() {
-        if (!MONET_ENABLED) {
-            return;
-        }
-        List<OverlayInfo> androidOverlays = mOverlayManager
-                .getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM);
-        for (OverlayInfo overlayInfo : androidOverlays) {
-            String packageName = overlayInfo.packageName;
-            if (DEBUG) {
-                Log.d(TAG, "Processing overlay " + packageName);
-            }
-            if (OVERLAY_CATEGORY_SYSTEM_PALETTE.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_SYSTEM_PALETTE_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_SYSTEM_PALETTE_PACKAGE, "");
-                    mMainSystemColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (OVERLAY_CATEGORY_ACCENT_COLOR.equals(overlayInfo.category)
-                    && packageName.startsWith(MONET_ACCENT_COLOR_PACKAGE)) {
-                try {
-                    String color = packageName.replace(MONET_ACCENT_COLOR_PACKAGE, "");
-                    mAccentColors.add(Integer.parseInt(color, 16));
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "Invalid package name for overlay " + packageName, e);
-                }
-            } else if (DEBUG) {
-                Log.d(TAG, "Unknown overlay: " + packageName + " category: "
-                        + overlayInfo.category);
-            }
-        }
-    }
-
-    /**
      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
      * affect sysui will also be applied to the system user.
      */
     void applyCurrentUserOverlays(
-            Map<String, String> categoryToPackage, Set<UserHandle> userHandles) {
+            Map<String, OverlayIdentifier> categoryToPackage,
+            FabricatedOverlay[] pendingCreation,
+            Set<UserHandle> userHandles) {
         // Disable all overlays that have not been specified in the user setting.
         final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
         overlayCategoriesToDisable.removeAll(categoryToPackage.keySet());
@@ -229,55 +166,68 @@
         final List<OverlayInfo> overlays = new ArrayList<>();
         targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
                 .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
-        final Map<String, String> overlaysToDisable = overlays.stream()
+        final List<Pair<String, String>> overlaysToDisable = overlays.stream()
                 .filter(o ->
                         mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
                 .filter(o -> overlayCategoriesToDisable.contains(o.category))
                 .filter(o -> o.isEnabled())
-                .collect(Collectors.toMap((o) -> o.category, (o) -> o.packageName));
+                .map(o -> new Pair<>(o.category, o.packageName))
+                .collect(Collectors.toList());
+
+        OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+        if (pendingCreation != null) {
+            for (FabricatedOverlay overlay : pendingCreation) {
+                transaction.registerFabricatedOverlay(overlay);
+            }
+        }
 
         // Toggle overlays in the order of THEME_CATEGORIES.
         for (String category : THEME_CATEGORIES) {
             if (categoryToPackage.containsKey(category)) {
-                setEnabled(categoryToPackage.get(category), category, userHandles, true);
-            } else if (overlaysToDisable.containsKey(category)) {
-                setEnabled(overlaysToDisable.get(category), category, userHandles, false);
+                OverlayIdentifier overlayInfo = categoryToPackage.get(category);
+                setEnabled(transaction, overlayInfo, category, userHandles, true);
             }
         }
-    }
-
-    private void setEnabled(
-            String packageName, String category, Set<UserHandle> handles, boolean enabled) {
-        for (UserHandle userHandle : handles) {
-            setEnabledAsync(packageName, userHandle, enabled);
+        for (Pair<String, String> packageToDisable : overlaysToDisable) {
+            OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+            setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false);
         }
-        if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
-            setEnabledAsync(packageName, UserHandle.SYSTEM, enabled);
-        }
-    }
 
-    private void setEnabledAsync(String pkg, UserHandle userHandle, boolean enabled) {
         mExecutor.execute(() -> {
-            if (DEBUG) Log.d(TAG, String.format("setEnabled: %s %s %b", pkg, userHandle, enabled));
             try {
-                if (enabled) {
-                    mOverlayManager.setEnabledExclusiveInCategory(pkg, userHandle);
-                } else {
-                    mOverlayManager.setEnabled(pkg, false, userHandle);
-                }
+                mOverlayManager.commit(transaction.build());
             } catch (SecurityException | IllegalStateException e) {
-                Log.e(TAG,
-                        String.format("setEnabled failed: %s %s %b", pkg, userHandle, enabled), e);
+                Log.e(TAG, "setEnabled failed", e);
             }
         });
     }
 
+    @VisibleForTesting
+    protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+        return new OverlayManagerTransaction.Builder();
+    }
+
+    private void setEnabled(OverlayManagerTransaction.Builder transaction,
+            OverlayIdentifier identifier, String category, Set<UserHandle> handles,
+            boolean enabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: "
+                    + category + ": " + enabled);
+        }
+        for (UserHandle userHandle : handles) {
+            transaction.setEnabled(identifier, enabled, userHandle.getIdentifier());
+        }
+        if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) {
+            transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier());
+        }
+    }
+
     /**
      * @inherit
      */
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("mMainSystemColors=" + mMainSystemColors.size());
-        pw.println("mAccentColors=" + mAccentColors.size());
+        pw.println("mTargetPackageToCategories=" + mTargetPackageToCategories);
+        pw.println("mCategoryToTargetPackage=" + mCategoryToTargetPackage);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d9f4744..522a42b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
@@ -25,6 +26,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.graphics.Color;
@@ -40,7 +43,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -48,6 +50,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -59,10 +62,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
@@ -77,9 +80,12 @@
  */
 @SysUISingleton
 public class ThemeOverlayController extends SystemUI implements Dumpable {
-    private static final String TAG = "ThemeOverlayController";
+    protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    protected static final int MAIN = 0;
+    protected static final int ACCENT = 1;
+
     // If lock screen wallpaper colors should also be considered when selecting the theme.
     // Doing this has performance impact, given that overlays would need to be swapped when
     // the device unlocks.
@@ -95,16 +101,19 @@
     private final Handler mBgHandler;
     private final WallpaperManager mWallpaperManager;
     private final KeyguardStateController mKeyguardStateController;
+    private final boolean mIsMonetEnabled;
     private WallpaperColors mLockColors;
     private WallpaperColors mSystemColors;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // If fabricated overlays were already created for the current theme.
+    private boolean mNeedsOverlayCreation;
+    // Dominant olor extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
-    // Color extracted from wallpaper, NOT the color used on the overlay
+    // Accent color extracted from wallpaper, NOT the color used on the overlay
     protected int mWallpaperAccentColor = Color.TRANSPARENT;
-    // Main system color that maps to an overlay color
-    private int mSystemOverlayColor = Color.TRANSPARENT;
-    // Accent color that maps to an overlay color
-    private int mAccentOverlayColor = Color.TRANSPARENT;
+    // System colors overlay
+    private FabricatedOverlay mSystemOverlay;
+    // Accent colors overlay
+    private FabricatedOverlay mAccentOverlay;
 
     @Inject
     public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@@ -112,9 +121,10 @@
             @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
             SecureSettings secureSettings, WallpaperManager wallpaperManager,
             UserManager userManager, KeyguardStateController keyguardStateController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, FeatureFlags featureFlags) {
         super(context);
 
+        mIsMonetEnabled = featureFlags.isMonetEnabled();
         mBroadcastDispatcher = broadcastDispatcher;
         mUserManager = userManager;
         mBgExecutor = bgExecutor;
@@ -221,20 +231,16 @@
         mMainWallpaperColor = mainColor;
         mWallpaperAccentColor = accentCandidate;
 
-        // Let's compare these colors to our finite set of overlays, and then pick an overlay.
-        List<Integer> systemColors = mThemeManager.getAvailableSystemColors();
-        List<Integer> accentColors = mThemeManager.getAvailableAccentColors();
-
-        if (systemColors.size() == 0 || accentColors.size() == 0) {
+        if (mIsMonetEnabled) {
+            mSystemOverlay = getOverlay(mMainWallpaperColor, MAIN);
+            mAccentOverlay = getOverlay(mWallpaperAccentColor, ACCENT);
+            mNeedsOverlayCreation = true;
             if (DEBUG) {
-                Log.d(TAG, "Cannot apply system theme, palettes are unavailable");
+                Log.d(TAG, "fetched overlays. system: " + mSystemOverlay + " accent: "
+                        + mAccentOverlay);
             }
-            return;
         }
 
-        mSystemOverlayColor = getClosest(systemColors, mMainWallpaperColor);
-        mAccentOverlayColor = getClosest(accentColors, mWallpaperAccentColor);
-
         updateThemeOverlays();
     }
 
@@ -257,42 +263,10 @@
     }
 
     /**
-     * Given a color and a list of candidates, return the candidate that's the most similar to the
-     * given color.
+     * Given a color candidate, return an overlay definition.
      */
-    protected int getClosest(List<Integer> candidates, int color) {
-        float[] hslMain = new float[3];
-        float[] hslCandidate = new float[3];
-
-        ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hslMain);
-        hslMain[0] /= 360f;
-
-        // To close to white or black, let's use the default system theme instead of
-        // applying a colorized one.
-        if (hslMain[2] < 0.05 || hslMain[2] > 0.95) {
-            return Color.TRANSPARENT;
-        }
-
-        float minDistance = Float.MAX_VALUE;
-        int closestColor = Color.TRANSPARENT;
-        for (int candidate: candidates) {
-            ColorUtils.RGBToHSL(Color.red(candidate), Color.green(candidate), Color.blue(candidate),
-                    hslCandidate);
-            hslCandidate[0] /= 360f;
-
-            float sqDistance = squared(hslCandidate[0] - hslMain[0])
-                    + squared(hslCandidate[1] - hslMain[1])
-                    + squared(hslCandidate[2] - hslMain[2]);
-            if (sqDistance < minDistance) {
-                minDistance = sqDistance;
-                closestColor = candidate;
-            }
-        }
-        return closestColor;
-    }
-
-    private static float squared(float f) {
-        return f * f;
+    protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
+        return null;
     }
 
     private void updateThemeOverlays() {
@@ -301,20 +275,15 @@
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 currentUser);
         if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson);
-        boolean hasSystemPalette = false;
-        boolean hasAccentColor = false;
-        final Map<String, String> categoryToPackage = new ArrayMap<>();
+        final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>();
         if (!TextUtils.isEmpty(overlayPackageJson)) {
             try {
                 JSONObject object = new JSONObject(overlayPackageJson);
                 for (String category : ThemeOverlayApplier.THEME_CATEGORIES) {
                     if (object.has(category)) {
-                        if (category.equals(OVERLAY_CATEGORY_ACCENT_COLOR)) {
-                            hasAccentColor = true;
-                        } else if (category.equals(OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
-                            hasSystemPalette = true;
-                        }
-                        categoryToPackage.put(category, object.getString(category));
+                        OverlayIdentifier identifier =
+                                new OverlayIdentifier(object.getString(category));
+                        categoryToPackage.put(category, identifier);
                     }
                 }
             } catch (JSONException e) {
@@ -322,17 +291,41 @@
             }
         }
 
-        // Let's apply the system palette, but only if it was not overridden by the style picker.
-        if (!hasSystemPalette && mSystemOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE,
-                    ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE
-                            + getColorString(mSystemOverlayColor));
+        // Let's generate system overlay if the style picker decided to override it.
+        OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+        if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16);
+                mSystemOverlay = getOverlay(color, MAIN);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName());
+            }
         }
-        // Same for the accent color
-        if (!hasAccentColor && mAccentOverlayColor != Color.TRANSPARENT) {
-            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR,
-                    ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE
-                            + getColorString(mAccentOverlayColor));
+
+        // Same for accent color.
+        OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR);
+        if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) {
+            try {
+                int color = Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16);
+                mAccentOverlay = getOverlay(color, ACCENT);
+                mNeedsOverlayCreation = true;
+                categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName());
+            }
+        }
+
+        // Compatibility with legacy themes, where full packages were defined, instead of just
+        // colors.
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)
+                && mSystemOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_SYSTEM_PALETTE, mSystemOverlay.getIdentifier());
+        }
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_ACCENT_COLOR)
+                && mAccentOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mAccentOverlay.getIdentifier());
         }
 
         Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser));
@@ -341,28 +334,31 @@
                 userHandles.add(userInfo.getUserHandle());
             }
         }
-        mThemeManager.applyCurrentUserOverlays(categoryToPackage, userHandles);
-    }
-
-    private String getColorString(int color) {
-        String colorString = Integer.toHexString(color).toUpperCase();
-        while (colorString.length() < 6) {
-            colorString = "0" + colorString;
+        if (DEBUG) {
+            Log.d(TAG, "Applying overlays: " + categoryToPackage.keySet().stream()
+                    .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
+                            Collectors.joining(", ")));
         }
-        // Remove alpha component
-        if (colorString.length() > 6) {
-            colorString = colorString.substring(colorString.length() - 6);
+        if (mNeedsOverlayCreation) {
+            mNeedsOverlayCreation = false;
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+                    mSystemOverlay, mAccentOverlay
+            }, userHandles);
+        } else {
+            mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles);
         }
-        return colorString;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("USE_LOCK_SCREEN_WALLPAPER=" + USE_LOCK_SCREEN_WALLPAPER);
         pw.println("mLockColors=" + mLockColors);
         pw.println("mSystemColors=" + mSystemColors);
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor));
-        pw.println("mSystemOverlayColor=" + Integer.toHexString(mSystemOverlayColor));
-        pw.println("mAccentOverlayColor=" + Integer.toHexString(mAccentOverlayColor));
+        pw.println("mSystemOverlayColor=" + mSystemOverlay);
+        pw.println("mAccentOverlayColor=" + mAccentOverlay);
+        pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+        pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index e9fcf1a..365cd2a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -20,10 +20,21 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
 import android.widget.ToastPresenter;
 
 import com.android.internal.R;
+import com.android.launcher3.icons.IconFactory;
 import com.android.systemui.plugins.ToastPlugin;
 
 /**
@@ -35,23 +46,43 @@
     final CharSequence mText;
     final ToastPlugin.Toast mPluginToast;
 
-    final int mDefaultGravity;
-    final int mDefaultY;
+    private final String mPackageName;
+    private final int mUserId;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
+
     final int mDefaultX = 0;
     final int mDefaultHorizontalMargin = 0;
     final int mDefaultVerticalMargin = 0;
 
-    SystemUIToast(Context context, CharSequence text) {
-        this(context, text, null);
+    private int mDefaultY;
+    private int mDefaultGravity;
+
+    @NonNull private final View mToastView;
+    @Nullable private final Animator mInAnimator;
+    @Nullable private final Animator mOutAnimator;
+
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            String packageName, int userId, boolean toastStyleEnabled, int orientation) {
+        this(layoutInflater, context, text, null, packageName, userId,
+                toastStyleEnabled, orientation);
     }
 
-    SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
+            ToastPlugin.Toast pluginToast, String packageName, int userId,
+            boolean toastStyleEnabled, int orientation) {
+        mToastStyleEnabled = toastStyleEnabled;
+        mLayoutInflater = layoutInflater;
         mContext = context;
         mText = text;
         mPluginToast = pluginToast;
+        mPackageName = packageName;
+        mUserId = userId;
+        mToastView = inflateToastView();
+        mInAnimator = createInAnimator();
+        mOutAnimator = createOutAnimator();
 
-        mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
-        mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+        onOrientationChange(orientation);
     }
 
     @Override
@@ -102,28 +133,19 @@
     @Override
     @NonNull
     public View getView() {
-        if (isPluginToast() && mPluginToast.getView() != null) {
-            return mPluginToast.getView();
-        }
-        return ToastPresenter.getTextToastView(mContext, mText);
+        return mToastView;
     }
 
     @Override
     @Nullable
     public Animator getInAnimation() {
-        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
-            return mPluginToast.getInAnimation();
-        }
-        return null;
+        return mInAnimator;
     }
 
     @Override
     @Nullable
     public Animator getOutAnimation() {
-        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
-            return mPluginToast.getOutAnimation();
-        }
-        return null;
+        return mOutAnimator;
     }
 
     /**
@@ -136,4 +158,80 @@
     private boolean isPluginToast() {
         return mPluginToast != null;
     }
+
+    private View inflateToastView() {
+        if (isPluginToast() && mPluginToast.getView() != null) {
+            return mPluginToast.getView();
+        }
+
+        View toastView;
+        if (mToastStyleEnabled) {
+            toastView = mLayoutInflater.inflate(
+                    com.android.systemui.R.layout.text_toast, null);
+            ((TextView) toastView.findViewById(com.android.systemui.R.id.text)).setText(mText);
+
+            ((ImageView) toastView.findViewById(com.android.systemui.R.id.icon))
+                    .setImageDrawable(getBadgedIcon(mContext, mPackageName, mUserId));
+        } else {
+            toastView = ToastPresenter.getTextToastView(mContext, mText);
+        }
+
+        return toastView;
+    }
+
+    /**
+     * Called on orientation changes to update parameters associated with the toast placement.
+     */
+    public void onOrientationChange(int orientation) {
+        if (mPluginToast != null) {
+            mPluginToast.onOrientationChange(orientation);
+        }
+
+        mDefaultY = mContext.getResources().getDimensionPixelSize(
+                mToastStyleEnabled
+                        ? com.android.systemui.R.dimen.toast_y_offset
+                        : R.dimen.toast_y_offset);
+        mDefaultGravity =
+                mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+    }
+
+    private Animator createInAnimator() {
+        if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+            return mPluginToast.getInAnimation();
+        }
+
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastIn(getView())
+                : null;
+    }
+
+    private Animator createOutAnimator() {
+        if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+            return mPluginToast.getOutAnimation();
+        }
+        return mToastStyleEnabled
+                ? ToastDefaultAnimation.Companion.toastOut(getView())
+                : null;
+    }
+
+    /**
+     * Get badged app icon if necessary, similar as used in the Settings UI.
+     * @return The icon to use
+     */
+    public static Drawable getBadgedIcon(@NonNull Context context, String packageName,
+            int userId) {
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
+            UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
+            IconFactory iconFactory = IconFactory.obtain(context);
+            Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
+                    appInfo.loadUnbadgedIcon(packageManager), user, false).icon;
+            return new BitmapDrawable(context.getResources(), iconBmp);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e("SystemUIToast", "could not load icon for package=" + packageName + " e=" + e);
+            return null;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
new file mode 100644
index 0000000..603d690
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast
+
+import android.animation.ObjectAnimator
+import android.view.View
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import android.animation.AnimatorSet
+
+class ToastDefaultAnimation {
+    /**
+     * sum of the in and out animation durations cannot exceed
+     * [com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER] to prevent the toast
+     * window from being removed before animations are completed
+     */
+    companion object {
+        // total duration shouldn't exceed NotificationManagerService's delay for "in" animation
+        fun toastIn(view: View): AnimatorSet? {
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0f, 0f, 0f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 0.9f, 1f).apply {
+                interpolator = scaleInterp
+                duration = 333
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 66
+            }
+            text.alpha = 0f // Set now otherwise won't apply until start delay
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            icon.alpha = 0f // Set now otherwise won't apply until start delay
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 0f, 1f).apply {
+                interpolator = linearInterp
+                duration = 283
+                startDelay = 50
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+
+        fun toastOut(view: View): AnimatorSet? {
+            // total duration shouldn't exceed NotificationManagerService's delay for "out" anim
+            val icon: View? = view.findViewById(com.android.systemui.R.id.icon)
+            val text: View? = view.findViewById(com.android.systemui.R.id.text)
+            if (icon == null || text == null) {
+                return null
+            }
+            val linearInterp = LinearInterpolator()
+            val scaleInterp = PathInterpolator(0.3f, 0f, 1f, 1f)
+            val sX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val sY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.9f).apply {
+                interpolator = scaleInterp
+                duration = 250
+            }
+            val vA = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 100
+                startDelay = 150
+            }
+            val tA = ObjectAnimator.ofFloat(text, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            val iA = ObjectAnimator.ofFloat(icon, "alpha", 1f, 0f).apply {
+                interpolator = linearInterp
+                duration = 166
+            }
+            return AnimatorSet().apply {
+                playTogether(sX, sY, vA, tA, iA)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index d8cb61c..8b782d4 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -17,6 +17,7 @@
 package com.android.systemui.toast;
 
 import android.content.Context;
+import android.view.LayoutInflater;
 
 import androidx.annotation.NonNull;
 
@@ -26,6 +27,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.ToastPlugin;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -40,10 +42,18 @@
 public class ToastFactory implements Dumpable {
     // only one ToastPlugin can be connected at a time.
     private ToastPlugin mPlugin;
+    private final LayoutInflater mLayoutInflater;
+    private final boolean mToastStyleEnabled;
 
     @Inject
-    public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+    public ToastFactory(
+            LayoutInflater layoutInflater,
+            PluginManager pluginManager,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
+        mLayoutInflater = layoutInflater;
         dumpManager.registerDumpable("ToastFactory", this);
+        mToastStyleEnabled = featureFlags.isToastStyleEnabled();
         pluginManager.addPluginListener(
                 new PluginListener<ToastPlugin>() {
                     @Override
@@ -64,11 +74,13 @@
      * Create a toast to be shown by ToastUI.
      */
     public SystemUIToast createToast(Context context, CharSequence text, String packageName,
-            int userId) {
+            int userId, int orientation) {
         if (isPluginAvailable()) {
-            return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+            return new SystemUIToast(mLayoutInflater, context, text, mPlugin.createToast(text,
+                    packageName, userId), packageName, userId, mToastStyleEnabled, orientation);
         }
-        return new SystemUIToast(context, text);
+        return new SystemUIToast(mLayoutInflater, context, text, packageName, userId,
+                mToastStyleEnabled, orientation);
     }
 
     private boolean isPluginAvailable() {
@@ -79,5 +91,6 @@
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("ToastFactory:");
         pw.println("    mAttachedPlugin=" + mPlugin);
+        pw.println("    mToastStyleEnabled=" + mToastStyleEnabled);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index 78173cf..51541bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -49,6 +49,15 @@
         })
     }
 
+    fun logOrientationChange(text: String, isPortrait: Boolean) {
+        log(DEBUG, {
+            str1 = text
+            bool1 = isPortrait
+        }, {
+            "Orientation change for toast. msg=\'$str1\' isPortrait=$bool1"
+        })
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 409d136..92ea1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,27 +16,28 @@
 
 package com.android.systemui.toast;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
 import android.animation.Animator;
 import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.ITransientNotificationCallback;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
-import android.widget.Toast;
 import android.widget.ToastPresenter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Objects;
 
@@ -58,18 +59,19 @@
     private final IAccessibilityManager mIAccessibilityManager;
     private final AccessibilityManager mAccessibilityManager;
     private final ToastFactory mToastFactory;
-    private final DelayableExecutor mMainExecutor;
     private final ToastLogger mToastLogger;
     private SystemUIToast mToast;
     @Nullable private ToastPresenter mPresenter;
     @Nullable private ITransientNotificationCallback mCallback;
+    private ToastOutAnimatorListener mToastOutAnimatorListener;
+
+    private int mOrientation = ORIENTATION_PORTRAIT;
 
     @Inject
     public ToastUI(
             Context context,
             CommandQueue commandQueue,
             ToastFactory toastFactory,
-            @Main DelayableExecutor mainExecutor,
             ToastLogger toastLogger) {
         this(context, commandQueue,
                 INotificationManager.Stub.asInterface(
@@ -77,21 +79,19 @@
                 IAccessibilityManager.Stub.asInterface(
                         ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
                 toastFactory,
-                mainExecutor,
                 toastLogger);
     }
 
     @VisibleForTesting
     ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
             @Nullable IAccessibilityManager accessibilityManager,
-            ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+            ToastFactory toastFactory, ToastLogger toastLogger
     ) {
         super(context);
         mCommandQueue = commandQueue;
         mNotificationManager = notificationManager;
         mIAccessibilityManager = accessibilityManager;
         mToastFactory = toastFactory;
-        mMainExecutor = mainExecutor;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mToastLogger = toastLogger;
     }
@@ -105,36 +105,38 @@
     @MainThread
     public void showToast(int uid, String packageName, IBinder token, CharSequence text,
             IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
-        if (mPresenter != null) {
-            hideCurrentToast();
-        }
-        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-        Context context = mContext.createContextAsUser(userHandle, 0);
-        mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+        Runnable showToastRunnable = () -> {
+            UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+            Context context = mContext.createContextAsUser(userHandle, 0);
+            mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
+                    userHandle.getIdentifier(), mOrientation);
 
-        if (mToast.hasCustomAnimation()) {
             if (mToast.getInAnimation() != null) {
                 mToast.getInAnimation().start();
             }
-            final Animator hideAnimator = mToast.getOutAnimation();
-            if (hideAnimator != null) {
-                final long durationMillis = duration == Toast.LENGTH_LONG
-                        ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
-                final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
-                        (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
-                mMainExecutor.executeDelayed(() -> hideAnimator.start(),
-                        updatedDuration - hideAnimator.getTotalDuration());
-            }
+
+            mCallback = callback;
+            mPresenter = new ToastPresenter(context, mIAccessibilityManager,
+                    mNotificationManager, packageName);
+            // Set as trusted overlay so touches can pass through toasts
+            mPresenter.getLayoutParams().setTrustedOverlay();
+            mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+            mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+                    mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+                    mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
+        };
+
+        if (mToastOutAnimatorListener != null) {
+            // if we're currently animating out a toast, show new toast after prev toast is hidden
+            mToastOutAnimatorListener.setShowNextToastRunnable(showToastRunnable);
+        } else if (mPresenter != null) {
+            // if there's a toast already showing that we haven't tried hiding yet, hide it and
+            // then show the next toast after its hidden animation is done
+            hideCurrentToast(showToastRunnable);
+        } else {
+            // else, show this next toast immediately
+            showToastRunnable.run();
         }
-        mCallback = callback;
-        mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
-                packageName);
-        // Set as trusted overlay so touches can pass through toasts
-        mPresenter.getLayoutParams().setTrustedOverlay();
-        mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
-        mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
-                mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
-                mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
     }
 
     @Override
@@ -146,12 +148,88 @@
             return;
         }
         mToastLogger.logOnHideToast(packageName, token.toString());
-        hideCurrentToast();
+        hideCurrentToast(null);
     }
 
     @MainThread
-    private void hideCurrentToast() {
-        mPresenter.hide(mCallback);
+    private void hideCurrentToast(Runnable runnable) {
+        if (mToast.getOutAnimation() != null) {
+            Animator animator = mToast.getOutAnimation();
+            mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback,
+                    runnable);
+            animator.addListener(mToastOutAnimatorListener);
+            animator.start();
+        } else {
+            mPresenter.hide(mCallback);
+            if (runnable != null) {
+                runnable.run();
+            }
+        }
+        mToast = null;
         mPresenter = null;
+        mCallback = null;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (newConfig.orientation != mOrientation) {
+            mOrientation = newConfig.orientation;
+            if (mToast != null) {
+                mToastLogger.logOrientationChange(mToast.mText.toString(),
+                        mOrientation == ORIENTATION_PORTRAIT);
+                mToast.onOrientationChange(mOrientation);
+                mPresenter.updateLayoutParams(
+                        mToast.getXOffset(),
+                        mToast.getYOffset(),
+                        mToast.getHorizontalMargin(),
+                        mToast.getVerticalMargin(),
+                        mToast.getGravity());
+            }
+        }
+    }
+
+    /**
+     * Once the out animation for a toast is finished, start showing the next toast.
+     */
+    class ToastOutAnimatorListener implements Animator.AnimatorListener {
+        final ToastPresenter mPrevPresenter;
+        final ITransientNotificationCallback mPrevCallback;
+        @Nullable Runnable mShowNextToastRunnable;
+
+        ToastOutAnimatorListener(
+                @NonNull ToastPresenter presenter,
+                @NonNull ITransientNotificationCallback callback,
+                @Nullable Runnable runnable) {
+            mPrevPresenter = presenter;
+            mPrevCallback = callback;
+            mShowNextToastRunnable = runnable;
+        }
+
+        void setShowNextToastRunnable(Runnable runnable) {
+            mShowNextToastRunnable = runnable;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mPrevPresenter.hide(mPrevCallback);
+            if (mShowNextToastRunnable != null) {
+                mShowNextToastRunnable.run();
+            }
+            mToastOutAnimatorListener = null;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            onAnimationEnd(animation);
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 09335af..b67574d 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -109,7 +109,7 @@
         dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel),
                 (OnClickListener) null);
         dialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                context.getString(R.string.qs_customize_remove), new OnClickListener() {
+                context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         // Tell the tuner (in main SysUI process) to clear all its settings.
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 72f1f22..fd3641c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -20,12 +20,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import java.util.HashSet;
 import java.util.List;
@@ -163,4 +166,15 @@
         }
         return apps;
     }
+
+    /**
+     * Returns true if the device should use the split notification shade, based on feature flags,
+     * orientation and screen width.
+     */
+    public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
+            Resources resources) {
+        return featureFlags.isTwoColumnNotificationShadeEnabled()
+                && resources.getBoolean(R.bool.config_use_split_notification_shade);
+    }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 6e7aed0..afeda967 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -45,6 +45,7 @@
 import android.service.notification.ZenModeConfig;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -86,10 +87,12 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 import java.util.function.Supplier;
 
@@ -248,38 +251,19 @@
                 });
 
         mSysuiProxy = new Bubbles.SysuiProxy() {
-            private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
-                    Class clazz) {
-                if (Looper.myLooper() == Looper.getMainLooper()) {
-                    return runnable.get();
-                }
-                final T[] result = (T[]) Array.newInstance(clazz, 1);
-                final CountDownLatch latch = new CountDownLatch(1);
-                executor.execute(() -> {
-                    result[0] = runnable.get();
-                    latch.countDown();
-                });
-                try {
-                    latch.await();
-                    return result[0];
-                } catch (InterruptedException e) {
-                    return null;
-                }
-            }
-
             @Override
-            @Nullable
-            public BubbleEntry getPendingOrActiveEntry(String key) {
-                return executeBlockingForResult(() -> {
+            public void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback) {
+                sysuiMainExecutor.execute(() -> {
                     NotificationEntry entry =
                             mNotificationEntryManager.getPendingOrActiveNotif(key);
-                    return entry == null ? null : notifToBubbleEntry(entry);
-                }, sysuiMainExecutor, BubbleEntry.class);
+                    callback.accept(entry == null ? null : notifToBubbleEntry(entry));
+                });
             }
 
             @Override
-            public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
-                return executeBlockingForResult(() -> {
+            public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+                    Consumer<List<BubbleEntry>> callback) {
+                sysuiMainExecutor.execute(() -> {
                     List<BubbleEntry> result = new ArrayList<>();
                     List<NotificationEntry> activeEntries =
                             mNotificationEntryManager.getActiveNotificationsForCurrentUser();
@@ -291,27 +275,8 @@
                             result.add(notifToBubbleEntry(entry));
                         }
                     }
-                    return result;
-                }, sysuiMainExecutor, List.class);
-            }
-
-            @Override
-            public boolean isNotificationShadeExpand() {
-                return executeBlockingForResult(() -> {
-                    return mNotificationShadeWindowController.getPanelExpanded();
-                }, sysuiMainExecutor, Boolean.class);
-            }
-
-            @Override
-            public boolean shouldBubbleUp(String key) {
-                return executeBlockingForResult(() -> {
-                    final NotificationEntry entry =
-                            mNotificationEntryManager.getPendingOrActiveNotif(key);
-                    if (entry != null) {
-                        return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
-                    }
-                    return false;
-                }, sysuiMainExecutor, Boolean.class);
+                    callback.accept(result);
+                });
             }
 
             @Override
@@ -587,7 +552,20 @@
     }
 
     void onRankingUpdate(RankingMap rankingMap) {
-        mBubbles.onRankingUpdated(rankingMap);
+        String[] orderedKeys = rankingMap.getOrderedKeys();
+        HashMap<String, Pair<BubbleEntry, Boolean>> pendingOrActiveNotif = new HashMap<>();
+        for (int i = 0; i < orderedKeys.length; i++) {
+            String key = orderedKeys[i];
+            NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+            BubbleEntry bubbleEntry = entry != null
+                    ? notifToBubbleEntry(entry)
+                    : null;
+            boolean shouldBubbleUp = entry != null
+                    ? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+                    : false;
+            pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
+        }
+        mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 0795d89..ff28819 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
@@ -28,6 +29,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -49,7 +51,7 @@
 import dagger.Provides;
 
 /**
- * Dagger module for TV Pip.
+ * Provides TV specific dependencies for Pip.
  */
 @Module(includes = {WMShellBaseModule.class})
 public abstract class TvPipModule {
@@ -143,7 +145,8 @@
             PipAnimationController pipAnimationController,
             PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index f23367b..141b9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -39,11 +40,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
 public class TvWMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -53,16 +63,20 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideSplitScreen(Context context,
+    static LegacySplitScreenController provideSplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 81ac21c..8505703 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
@@ -73,7 +74,20 @@
 import javax.inject.Inject;
 
 /**
- * Proxy in SysUiScope to delegate events to controllers in WM Shell library.
+ * A SystemUI service that starts with the SystemUI application and sets up any bindings between
+ * Shell and SysUI components.  This service starts happens after the {@link WMComponent} has
+ * already been initialized and may only reference Shell components that are explicitly exported to
+ * SystemUI (see {@link WMComponent}.
+ *
+ * eg. SysUI application starts
+ *     -> SystemUIFactory is initialized
+ *       -> WMComponent is created
+ *         -> WMShellBaseModule dependencies are injected
+ *         -> WMShellModule (form-factory specific) dependencies are injected
+ *       -> SysUIComponent is created
+ *         -> WMComponents are explicitly provided to SysUIComponent for injection into SysUI code
+ *     -> SysUI services are started
+ *       -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces
  */
 @SysUISingleton
 public final class WMShell extends SystemUI
@@ -142,6 +156,8 @@
 
     @Override
     public void start() {
+        // TODO: Consider piping config change and other common calls to a shell component to
+        //  delegate internally
         mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
@@ -350,7 +366,7 @@
             switch (args[i]) {
                 case "enable-text": {
                     String[] groups = Arrays.copyOfRange(args, i + 1, args.length);
-                    int result = protoLogImpl.startTextLogging(mContext, groups, pw);
+                    int result = protoLogImpl.startTextLogging(groups, pw);
                     if (result == 0) {
                         pw.println("Starting logging on groups: " + Arrays.toString(groups));
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index b42dde6..fba0b00 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -20,7 +20,6 @@
 
 import android.animation.AnimationHandler;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -32,6 +31,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.FullscreenTaskListener;
@@ -45,6 +45,7 @@
 import com.android.wm.shell.TaskViewFactoryController;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -63,6 +64,7 @@
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
@@ -71,7 +73,6 @@
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -85,8 +86,13 @@
 import dagger.Provides;
 
 /**
- * Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
- * should be shared among different branches of SystemUI.
+ * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines *common* dependencies across various SystemUI implementations,
+ * dependencies that are device/form factor SystemUI implementation specific should go into their
+ * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
 @Module
 public abstract class WMShellBaseModule {
@@ -174,53 +180,9 @@
         }
     }
 
-    @WMSingleton
-    @Provides
-    static ShellInit provideShellInit(DisplayImeController displayImeController,
-            DragAndDropController dragAndDropController,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<AppPairs> appPairsOptional,
-            FullscreenTaskListener fullscreenTaskListener,
-            Transitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return ShellInitImpl.create(displayImeController,
-                dragAndDropController,
-                shellTaskOrganizer,
-                legacySplitScreenOptional,
-                splitScreenOptional,
-                appPairsOptional,
-                fullscreenTaskListener,
-                transitions,
-                mainExecutor);
-    }
-
-    /**
-     * Note, this is only optional because we currently pass this to the SysUI component scope and
-     * for non-primary users, we may inject a null-optional for that dependency.
-     */
-    @WMSingleton
-    @Provides
-    static Optional<ShellCommandHandler> provideShellCommandHandler(
-            ShellTaskOrganizer shellTaskOrganizer,
-            Optional<LegacySplitScreen> legacySplitScreenOptional,
-            Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<OneHanded> oneHandedOptional,
-            Optional<HideDisplayCutout> hideDisplayCutout,
-            Optional<AppPairs> appPairsOptional,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
-                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
-                hideDisplayCutout, appPairsOptional, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static TransactionPool provideTransactionPool() {
-        return new TransactionPool();
-    }
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
 
     @WMSingleton
     @Provides
@@ -238,8 +200,45 @@
 
     @WMSingleton
     @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
+    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
+            Context context, SizeCompatUIController sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUIController provideSizeCompatUIController(Context context,
+            DisplayController displayController, DisplayImeController imeController,
+            SyncTransactionQueue syncQueue) {
+        return new SizeCompatUIController(context, displayController, imeController, syncQueue);
+    }
+
+    @WMSingleton
+    @Provides
+    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new SyncTransactionQueue(pool, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static SystemWindows provideSystemWindows(DisplayController displayController,
+            IWindowManager wmService) {
+        return new SystemWindows(displayController, wmService);
+    }
+
+    // We currently dedupe multiple messages, so we use the shell main handler directly
+    @WMSingleton
+    @Provides
+    static TaskStackListenerImpl providerTaskStackListenerImpl(
+            @ShellMainThread Handler mainHandler) {
+        return new TaskStackListenerImpl(mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static TransactionPool provideTransactionPool() {
+        return new TransactionPool();
     }
 
     @WMSingleton
@@ -249,10 +248,99 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    //
+    // Bubbles
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<Bubbles> provideBubbles(Optional<BubbleController> bubbleController) {
+        return bubbleController.map((controller) -> controller.asBubbles());
+    }
+
+    // Note: Handler needed for LauncherApps.register
+    @WMSingleton
+    @Provides
+    static Optional<BubbleController> provideBubbleController(Context context,
+            FloatingContentCoordinator floatingContentCoordinator,
+            IStatusBarService statusBarService,
+            WindowManager windowManager,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            ShellTaskOrganizer organizer,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.of(BubbleController.create(context, null /* synchronizer */,
+                floatingContentCoordinator, statusBarService, windowManager,
+                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
+                mainExecutor, mainHandler));
+    }
+
+    //
+    // Fullscreen
+    //
+
+    @WMSingleton
+    @Provides
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
+        return new FullscreenTaskListener(syncQueue);
+    }
+
+    //
+    // Hide display cutout
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutout> provideHideDisplayCutout(
+            Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
+        return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
+    }
+
+    @WMSingleton
+    @Provides
+    static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
+            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.ofNullable(
+                HideDisplayCutoutController.create(context, displayController, mainExecutor));
+    }
+
+    //
+    // One handed mode (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<OneHanded> provideOneHanded(Optional<OneHandedController> oneHandedController) {
+        return oneHandedController.map((controller) -> controller.asOneHanded());
+    }
+
+    // Needs the shell main handler for ContentObserver callbacks
+    @WMSingleton
+    @Provides
+    static Optional<OneHandedController> provideOneHandedController(Context context,
+            DisplayController displayController, TaskStackListenerImpl taskStackListener,
+            UiEventLogger uiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return Optional.ofNullable(OneHandedController.create(context, displayController,
+                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    }
+
+    //
+    // Pip (optional feature)
+    //
+
+    @WMSingleton
+    @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
     @WMSingleton
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
-            IActivityManager activityManager,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
@@ -268,37 +356,38 @@
 
     @WMSingleton
     @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @WMSingleton
-    @Provides
     static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
         return new PipSurfaceTransactionHelper(context);
     }
 
     @WMSingleton
     @Provides
-    static SystemWindows provideSystemWindows(DisplayController displayController,
-            IWindowManager wmService) {
-        return new SystemWindows(displayController, wmService);
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+
+    //
+    // Shell transitions
+    //
+
+    @WMSingleton
+    @Provides
+    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
+        return Transitions.asRemoteTransitions(transitions);
     }
 
     @WMSingleton
     @Provides
-    static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new SyncTransactionQueue(pool, mainExecutor);
+    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
 
-    @WMSingleton
-    @Provides
-    static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context, SizeCompatUI sizeCompatUI) {
-        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
-    }
+    //
+    // Split/multiwindow
+    //
 
     @WMSingleton
     @Provides
@@ -307,17 +396,6 @@
         return new RootTaskDisplayAreaOrganizer(mainExecutor, context);
     }
 
-    // We currently dedupe multiple messages, so we use the shell main handler directly
-    @WMSingleton
-    @Provides
-    static TaskStackListenerImpl providerTaskStackListenerImpl(
-            @ShellMainThread Handler mainHandler) {
-        return new TaskStackListenerImpl(mainHandler);
-    }
-
-    @BindsOptionalOf
-    abstract LegacySplitScreen optionalLegacySplitScreen();
-
     @WMSingleton
     @Provides
     static Optional<SplitScreen> provideSplitScreen(
@@ -340,81 +418,91 @@
         }
     }
 
+    // Legacy split (optional feature)
+
+    @WMSingleton
+    @Provides
+    static Optional<LegacySplitScreen> provideLegacySplitScreen(
+            Optional<LegacySplitScreenController> splitScreenController) {
+        return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
+    }
+
     @BindsOptionalOf
-    abstract AppPairs optionalAppPairs();
+    abstract LegacySplitScreenController optionalLegacySplitScreenController();
 
-    // Note: Handler needed for LauncherApps.register
+    // App Pairs (optional feature)
+
     @WMSingleton
     @Provides
-    static Optional<Bubbles> provideBubbles(Context context,
-            FloatingContentCoordinator floatingContentCoordinator,
-            IStatusBarService statusBarService,
-            WindowManager windowManager,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps,
-            UiEventLogger uiEventLogger,
-            ShellTaskOrganizer organizer,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.of(BubbleController.create(context, null /* synchronizer */,
-                floatingContentCoordinator, statusBarService, windowManager,
-                windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
-                mainExecutor, mainHandler));
+    static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
+        return appPairsController.map((controller) -> controller.asAppPairs());
     }
 
-    // Needs the shell main handler for ContentObserver callbacks
+    @BindsOptionalOf
+    abstract AppPairsController optionalAppPairs();
+
+    //
+    // Task view factory
+    //
+
     @WMSingleton
     @Provides
-    static Optional<OneHanded> provideOneHandedController(Context context,
-            DisplayController displayController, TaskStackListenerImpl taskStackListener,
-            UiEventLogger uiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return Optional.ofNullable(OneHandedController.create(context, displayController,
-                taskStackListener, uiEventLogger, mainExecutor, mainHandler));
+    static Optional<TaskViewFactory> provideTaskViewFactory(
+            TaskViewFactoryController taskViewFactoryController) {
+        return Optional.of(taskViewFactoryController.asTaskViewFactory());
     }
 
     @WMSingleton
     @Provides
-    static Optional<HideDisplayCutout> provideHideDisplayCutoutController(Context context,
-            DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(
-                HideDisplayCutoutController.create(context, displayController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<TaskViewFactory> provideTaskViewFactory(ShellTaskOrganizer shellTaskOrganizer,
+    static TaskViewFactoryController provideTaskViewFactoryController(
+            ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(new TaskViewFactoryController(shellTaskOrganizer, mainExecutor)
-                .getTaskViewFactory());
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
     }
 
+    //
+    // Misc
+    //
+
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
-        return new FullscreenTaskListener(syncQueue);
-    }
-
-    @WMSingleton
-    @Provides
-    static RemoteTransitions provideRemoteTransitions(Transitions transitions) {
-        return Transitions.asRemoteTransitions(transitions);
-    }
-
-    @WMSingleton
-    @Provides
-    static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellAnimationThread ShellExecutor animExecutor) {
-        return new Transitions(organizer, pool, mainExecutor, animExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
-            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
-        return SizeCompatUIController.create(context, displayController, imeController,
+    static ShellInit provideShellInit(DisplayImeController displayImeController,
+            DragAndDropController dragAndDropController,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<AppPairsController> appPairsOptional,
+            FullscreenTaskListener fullscreenTaskListener,
+            Transitions transitions,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return ShellInitImpl.create(displayImeController,
+                dragAndDropController,
+                shellTaskOrganizer,
+                legacySplitScreenOptional,
+                splitScreenOptional,
+                appPairsOptional,
+                fullscreenTaskListener,
+                transitions,
                 mainExecutor);
     }
+
+    /**
+     * Note, this is only optional because we currently pass this to the SysUI component scope and
+     * for non-primary users, we may inject a null-optional for that dependency.
+     */
+    @WMSingleton
+    @Provides
+    static Optional<ShellCommandHandler> provideShellCommandHandler(
+            ShellTaskOrganizer shellTaskOrganizer,
+            Optional<LegacySplitScreenController> legacySplitScreenOptional,
+            Optional<SplitScreenController> splitScreenOptional,
+            Optional<Pip> pipOptional,
+            Optional<OneHandedController> oneHandedOptional,
+            Optional<HideDisplayCutoutController> hideDisplayCutout,
+            Optional<AppPairsController> appPairsOptional,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.of(ShellCommandHandlerImpl.create(shellTaskOrganizer,
+                legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
+                hideDisplayCutout, appPairsOptional, mainExecutor));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 2aaa095..997b488 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -21,10 +21,10 @@
 import android.os.Handler;
 import android.view.IWindowManager;
 
+import com.android.systemui.dagger.WMComponent;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
@@ -36,7 +36,6 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
 import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
@@ -60,11 +59,20 @@
 import dagger.Provides;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell} which could be customized among different
- * branches of SystemUI.
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
+ * accessible from components within the WM subcomponent (can be explicitly exposed to the
+ * SysUIComponent, see {@link WMComponent}).
+ *
+ * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = WMShellBaseModule.class)
 public class WMShellModule {
+
+    //
+    // Internal common - Components used internally by multiple shell features
+    //
+
     @WMSingleton
     @Provides
     static DisplayImeController provideDisplayImeController(IWindowManager wmService,
@@ -74,29 +82,37 @@
                 transactionPool);
     }
 
+    //
+    // Split/multiwindow
+    //
+
     @WMSingleton
     @Provides
-    static LegacySplitScreen provideLegacySplitScreen(Context context,
+    static LegacySplitScreenController provideLegacySplitScreen(Context context,
             DisplayController displayController, SystemWindows systemWindows,
             DisplayImeController displayImeController, TransactionPool transactionPool,
             ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
             TaskStackListenerImpl taskStackListener, Transitions transitions,
             @ShellMainThread ShellExecutor mainExecutor,
             @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
-        return LegacySplitScreenController.create(context, displayController, systemWindows,
+        return new LegacySplitScreenController(context, displayController, systemWindows,
                 displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
                 taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
     }
 
     @WMSingleton
     @Provides
-    static AppPairs provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
+    static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, DisplayController displayController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return AppPairsController.create(shellTaskOrganizer, syncQueue, displayController,
+        return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
                 mainExecutor);
     }
 
+    //
+    // Pip
+    //
+
     @WMSingleton
     @Provides
     static Optional<Pip> providePip(Context context, DisplayController displayController,
@@ -161,7 +177,8 @@
             PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             PipTransitionController pipTransitionController,
-            Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
+            Optional<LegacySplitScreenController> splitScreenOptional,
+            DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index aa4122f..d3f9d641 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -54,7 +54,6 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -74,7 +73,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class CarrierTextControllerTest extends SysuiTestCase {
+public class CarrierTextManagerTest extends SysuiTestCase {
 
     private static final CharSequence SEPARATOR = " \u2014 ";
     private static final CharSequence INVALID_CARD_TEXT = "Invalid card";
@@ -95,7 +94,9 @@
     @Mock
     private WifiManager mWifiManager;
     @Mock
-    private CarrierTextController.CarrierTextCallback mCarrierTextCallback;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private CarrierTextManager.CarrierTextCallback mCarrierTextCallback;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
@@ -104,13 +105,14 @@
     private TelephonyManager mTelephonyManager;
     @Mock
     private SubscriptionManager mSubscriptionManager;
-    private CarrierTextController.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
+    private CarrierTextManager.CarrierTextCallbackInfo mCarrierTextCallbackInfo;
 
-    private CarrierTextController mCarrierTextController;
+    private CarrierTextManager mCarrierTextManager;
     private TestableLooper mTestableLooper;
+    private Handler mMainHandler;
 
     private Void checkMainThread(InvocationOnMock inv) {
-        Looper mainLooper = Dependency.get(Dependency.MAIN_HANDLER).getLooper();
+        Looper mainLooper = mMainHandler.getLooper();
         if (!mainLooper.isCurrentThread()) {
             fail("This call should be done from the main thread");
         }
@@ -122,35 +124,33 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
 
-        mContext.addMockSystemService(WifiManager.class, mWifiManager);
-        mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager);
+        mMainHandler = new Handler(mTestableLooper.getLooper());
         when(mConnectivityManager.isNetworkSupported(anyInt())).thenReturn(true);
-        mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
-        mContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
         mContext.getOrCreateTestableResources().addOverride(
                 R.string.keyguard_sim_error_message_short, INVALID_CARD_TEXT);
         mContext.getOrCreateTestableResources().addOverride(
                 R.string.airplane_mode, AIRPLANE_MODE_TEXT);
-        mDependency.injectMockDependency(WakefulnessLifecycle.class);
-        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
-                new Handler(mTestableLooper.getLooper()));
-        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
-        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
 
         doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
                 .registerCallback(any(KeyguardUpdateMonitorCallback.class));
         doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
                 .removeCallback(any(KeyguardUpdateMonitorCallback.class));
 
-        mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
+        mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
         when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
         when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
 
-        mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
+        mCarrierTextManager = new CarrierTextManager.Builder(
+                mContext, mContext.getResources(), mWifiManager, mConnectivityManager,
+                mTelephonyManager, mWakefulnessLifecycle, new Handler(mTestableLooper.getLooper()),
+                mMainHandler, mKeyguardUpdateMonitor)
+                .setShowAirplaneMode(true)
+                .setShowMissingSim(true)
+                .build();
         // This should not start listening on any of the real dependencies but will test that
         // callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
-        mCarrierTextController.setListening(mCarrierTextCallback);
+        mCarrierTextManager.setListening(mCarrierTextCallback);
         mTestableLooper.processAllMessages();
     }
 
@@ -165,8 +165,8 @@
         TestableLooper testableLooper = new TestableLooper(thread.getLooper());
         Handler h = new Handler(testableLooper.getLooper());
         h.post(() -> {
-            mCarrierTextController.setListening(null);
-            mCarrierTextController.setListening(mCarrierTextCallback);
+            mCarrierTextManager.setListening(null);
+            mCarrierTextManager.setListening(mCarrierTextCallback);
         });
         testableLooper.processAllMessages();
         mTestableLooper.processAllMessages();
@@ -183,11 +183,11 @@
         when(mKeyguardUpdateMonitor.getSimState(0)).thenReturn(TelephonyManager.SIM_STATE_READY);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -205,12 +205,12 @@
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        mCarrierTextController.mCallback.onSimStateChanged(3, 1,
+        mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
@@ -223,7 +223,7 @@
         reset(mCarrierTextCallback);
         when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
         // Update carrier text. It should ignore error state of subId 3 in inactive slotId.
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals("TEST_CARRIER", captor.getValue().carrierText);
@@ -237,9 +237,9 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
-        mCarrierTextController.mCallback.onSimStateChanged(0, -3,
+        mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
-        mCarrierTextController.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
         verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
     }
 
@@ -257,23 +257,23 @@
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         // This should not produce an out of bounds error, even though there are no subscriptions
-        mCarrierTextController.mCallback.onSimStateChanged(0, 1,
+        mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
                 TelephonyManager.SIM_STATE_CARD_IO_ERROR);
 
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(
-                any(CarrierTextController.CarrierTextCallbackInfo.class));
+                any(CarrierTextManager.CarrierTextCallbackInfo.class));
     }
 
     @Test
     public void testCallback() {
         reset(mCarrierTextCallback);
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
         mTestableLooper.processAllMessages();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
         assertEquals(mCarrierTextCallbackInfo, captor.getValue());
     }
@@ -282,8 +282,8 @@
     public void testNullingCallback() {
         reset(mCarrierTextCallback);
 
-        mCarrierTextController.postToCallback(mCarrierTextCallbackInfo);
-        mCarrierTextController.setListening(null);
+        mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
+        mCarrierTextManager.setListening(null);
 
         // This shouldn't produce NPE
         mTestableLooper.processAllMessages();
@@ -301,15 +301,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(1, info.listOfCarriers.length);
         assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
         assertEquals(1, info.subscriptionIds.length);
@@ -326,15 +326,15 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(1, info.listOfCarriers.length);
         assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
         assertEquals(1, info.subscriptionIds.length);
@@ -346,16 +346,16 @@
         List<SubscriptionInfo> list = new ArrayList<>();
         list.add(TEST_SUBSCRIPTION_NULL);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-            TelephonyManager.SIM_STATE_READY);
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -380,11 +380,11 @@
         when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -407,15 +407,15 @@
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
                 new ArrayList<>());
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
-        CarrierTextController.CarrierTextCallbackInfo info = captor.getValue();
+        CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
         assertEquals(0, info.listOfCarriers.length);
         assertEquals(0, info.subscriptionIds.length);
 
@@ -428,16 +428,16 @@
         list.add(TEST_SUBSCRIPTION);
         list.add(TEST_SUBSCRIPTION);
         when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
-            TelephonyManager.SIM_STATE_READY);
+                TelephonyManager.SIM_STATE_READY);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -458,11 +458,11 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -483,11 +483,11 @@
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
@@ -509,11 +509,11 @@
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
 
-        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+        ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
                 ArgumentCaptor.forClass(
-                        CarrierTextController.CarrierTextCallbackInfo.class);
+                        CarrierTextManager.CarrierTextCallbackInfo.class);
 
-        mCarrierTextController.updateCarrierText();
+        mCarrierTextManager.updateCarrierText();
         mTestableLooper.processAllMessages();
         verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index c2ade81..d67fe6d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -69,6 +69,8 @@
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     @Mock
     private LatencyTracker mLatencyTracker;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
 
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
 
@@ -84,7 +86,8 @@
                 .thenReturn(mKeyguardMessageArea);
         mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mKeyguardMessageAreaControllerFactory, mLatencyTracker) {
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker,
+                mEmergencyButtonController) {
             @Override
             void resetState() {
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a2eaea1..70a7b7a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -34,6 +34,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -75,6 +76,8 @@
     NotificationIconAreaController mNotificationIconAreaController;
     @Mock
     ContentResolver mContentResolver;
+    @Mock
+    BroadcastDispatcher mBroadcastDispatcher;
 
     private KeyguardClockSwitchController mController;
 
@@ -94,7 +97,8 @@
                 mClockManager,
                 mKeyguardSliceViewController,
                 mNotificationIconAreaController,
-                mContentResolver);
+                mContentResolver,
+                mBroadcastDispatcher);
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 826be2b..4beec57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -54,11 +54,11 @@
 public class KeyguardDisplayManagerTest extends SysuiTestCase {
 
     @Mock
+    private NavigationBarController mNavigationBarController;
+    @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-
     @Mock
     private DisplayManager mDisplayManager;
-
     @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
 
@@ -76,9 +76,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
-        mDependency.injectMockDependency(NavigationBarController.class);
-        mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory,
-                mBackgroundExecutor));
+        mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
+                mKeyguardStatusViewComponentFactory, mBackgroundExecutor));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index c69ec1a..6d0c640 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -49,6 +49,8 @@
     @Mock
     private lateinit var mLatencyTracker: LatencyTracker
     @Mock
+    private lateinit var mEmergencyButtonController: EmergencyButtonController
+    @Mock
     private lateinit
     var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
     @Mock
@@ -72,7 +74,8 @@
                 .thenReturn(mKeyguardMessageAreaController)
         mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
         mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mLatencyTracker, mKeyguardMessageAreaControllerFactory)
+                mLatencyTracker, mEmergencyButtonController,
+                mKeyguardMessageAreaControllerFactory)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 31cc7bb..8d1e1a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -67,6 +67,8 @@
     private LatencyTracker mLatencyTracker;
     @Mock
     private LiftToActivateListener mLiftToactivateListener;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
     private View mDeleteButton;
@@ -92,7 +94,7 @@
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
-                mFalsingCollector) {
+                mEmergencyButtonController, mFalsingCollector) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 3b7f4b8..9296d3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -59,6 +59,10 @@
     @Mock
     private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
     @Mock
+    private EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
+    @Mock
+    private EmergencyButtonController mEmergencyButtonController;
+    @Mock
     private KeyguardInputViewController mKeyguardInputViewController;
     @Mock
     private KeyguardInputView mInputView;
@@ -76,9 +80,12 @@
                 any(KeyguardSecurityCallback.class)))
                 .thenReturn(mKeyguardInputViewController);
         when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class)))
+                .thenReturn(mEmergencyButtonController);
 
         mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
-                mLayoutInflater, mKeyguardSecurityViewControllerFactory);
+                mLayoutInflater, mKeyguardSecurityViewControllerFactory,
+                mEmergencyButtonControllerFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
index 53d84db..7b4f14d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
@@ -34,9 +34,9 @@
 
 import kotlin.math.ceil
 
-private val PAINT = arrayListOf(TextPaint().apply {
+private val PAINT = TextPaint().apply {
     textSize = 32f
-})
+}
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -49,10 +49,10 @@
 
     @Test
     fun testAnimationStarted() {
-        val layout = makeLayout("Hello, World", PAINT[0])
+        val layout = makeLayout("Hello, World", PAINT)
         val valueAnimator = mock(ValueAnimator::class.java)
         val textInterpolator = mock(TextInterpolator::class.java)
-        val paint = arrayListOf(mock(TextPaint::class.java))
+        val paint = mock(TextPaint::class.java)
         `when`(textInterpolator.targetPaint).thenReturn(paint)
 
         val textAnimator = TextAnimator(layout, {}).apply {
@@ -81,10 +81,10 @@
 
     @Test
     fun testAnimationNotStarted() {
-        val layout = makeLayout("Hello, World", PAINT[0])
+        val layout = makeLayout("Hello, World", PAINT)
         val valueAnimator = mock(ValueAnimator::class.java)
         val textInterpolator = mock(TextInterpolator::class.java)
-        val paint = arrayListOf(mock(TextPaint::class.java))
+        val paint = mock(TextPaint::class.java)
         `when`(textInterpolator.targetPaint).thenReturn(paint)
 
         val textAnimator = TextAnimator(layout, {}).apply {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index 1206dab..149e179 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -21,9 +21,9 @@
 import android.testing.AndroidTestingRunner
 import android.text.Layout
 import android.text.StaticLayout
-import android.text.TextPaint
 import android.text.TextDirectionHeuristic
 import android.text.TextDirectionHeuristics
+import android.text.TextPaint
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
@@ -40,13 +40,13 @@
     textSize = 32f
 }
 
-private val START_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val START_PAINT = TextPaint(PAINT).apply {
     fontVariationSettings = "'wght' 400"
-})
+}
 
-private val END_PAINT = arrayListOf(TextPaint(PAINT).apply {
+private val END_PAINT = TextPaint(PAINT).apply {
     fontVariationSettings = "'wght' 700"
-})
+}
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -67,16 +67,16 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(TEXT, START_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
     }
@@ -86,15 +86,15 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         interp.progress = 1f
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(TEXT, END_PAINT[0]).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+        val expected = makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
     }
@@ -104,10 +104,10 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // We cannot expect exact text layout of the middle position since we don't use text shaping
@@ -115,9 +115,9 @@
         // end state.
         interp.progress = 0.5f
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0])
+        assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT)
             .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
-        assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0])
+        assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT)
             .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
     }
 
@@ -126,10 +126,10 @@
         val layout = makeLayout(TEXT, PAINT)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         interp.progress = 0.5f
@@ -148,16 +148,16 @@
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.LTR)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.LTR)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.LTR)
                 .toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
@@ -168,16 +168,16 @@
         val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
 
         val interp = TextInterpolator(layout)
-        TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
+        interp.basePaint.set(START_PAINT)
         interp.onBasePaintModified()
 
-        TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
+        interp.targetPaint.set(END_PAINT)
         interp.onTargetPaintModified()
 
         // Just after created TextInterpolator, it should have 0 progress.
         assertThat(interp.progress).isEqualTo(0f)
         val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
-        val expected = makeLayout(BIDI_TEXT, START_PAINT[0], TextDirectionHeuristics.RTL)
+        val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
                 .toBitmap(BMP_WIDTH, BMP_HEIGHT)
 
         assertThat(expected.sameAs(actual)).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 0c69ffd..10332bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -100,6 +100,7 @@
         return new ImageWallpaper() {
             @Override
             public Engine onCreateEngine() {
+                onCreate();
                 return new GLEngine(mHandler) {
                     @Override
                     public Context getDisplayContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
new file mode 100644
index 0000000..9278570
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.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.systemui.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.wm.shell.TaskViewFactory
+import dagger.Lazy
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlActionCoordinatorImplTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var uiController: ControlsUiController
+    @Mock
+    private lateinit var lazyUiController: Lazy<ControlsUiController>
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var bgExecutor: DelayableExecutor
+    @Mock
+    private lateinit var uiExecutor: DelayableExecutor
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var globalActionsComponent: GlobalActionsComponent
+    @Mock
+    private lateinit var taskViewFactory: Optional<TaskViewFactory>
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var cvh: ControlViewHolder
+
+    companion object {
+        fun <T> any(): T = Mockito.any<T>()
+
+        private val ID = "id"
+    }
+
+    private lateinit var coordinator: ControlActionCoordinatorImpl
+    private lateinit var action: ControlActionCoordinatorImpl.Action
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coordinator = spy(ControlActionCoordinatorImpl(
+            mContext,
+            bgExecutor,
+            uiExecutor,
+            activityStarter,
+            keyguardStateController,
+            globalActionsComponent,
+            taskViewFactory,
+            getFakeBroadcastDispatcher(),
+            lazyUiController
+        ))
+
+        `when`(cvh.cws.ci.controlId).thenReturn(ID)
+        action = spy(coordinator.Action(ID, {}, false))
+        doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
+    }
+
+    @Test
+    fun testToggleRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(action).invoke()
+    }
+
+    @Test
+    fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
+        `when`(keyguardStateController.isShowing()).thenReturn(true)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+
+        coordinator.toggle(cvh, "", true)
+        verify(coordinator).bouncerOrRun(action)
+        verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+        verify(action, never()).invoke()
+
+        // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action, never()).invoke()
+
+        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+        reset(action)
+        coordinator.runPendingAction(ID)
+        verify(action).invoke()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 7fe6827..b8f91b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -17,11 +17,18 @@
 package com.android.systemui.controls.dagger
 
 import android.testing.AndroidTestingRunner
+import android.provider.Settings
 import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
 import dagger.Lazy
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -29,7 +36,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Answers
 import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -42,20 +53,29 @@
     private lateinit var uiController: ControlsUiController
     @Mock
     private lateinit var listingController: ControlsListingController
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+
+    companion object {
+        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+    }
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        `when`(userTracker.userHandle.identifier).thenReturn(0)
     }
 
     @Test
     fun testFeatureEnabled() {
-        val component = ControlsComponent(
-                true,
-                Lazy { controller },
-                Lazy { uiController },
-                Lazy { listingController }
-        )
+        val component = setupComponent(true)
 
         assertTrue(component.getControlsController().isPresent)
         assertEquals(controller, component.getControlsController().get())
@@ -67,15 +87,80 @@
 
     @Test
     fun testFeatureDisabled() {
-        val component = ControlsComponent(
-                false,
-                Lazy { controller },
-                Lazy { uiController },
-                Lazy { listingController }
-        )
+        val component = setupComponent(false)
 
         assertFalse(component.getControlsController().isPresent)
         assertFalse(component.getControlsUiController().isPresent)
         assertFalse(component.getControlsListingController().isPresent)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testFeatureDisabledVisibility() {
+        val component = setupComponent(false)
+
+        assertEquals(ControlsComponent.Visibility.UNAVAILABLE, component.getVisibility())
+    }
+
+    @Test
+    fun testFeatureEnabledAfterBootVisibility() {
+        `when`(controller.available).thenReturn(true)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT)
+        val component = setupComponent(true)
+
+        assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
+    }
+
+    @Test
+    fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() {
+        `when`(controller.available).thenReturn(true)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+            .thenReturn(0)
+        val component = setupComponent(true)
+
+        assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
+    }
+
+    @Test
+    fun testFeatureEnabledAndCanShowOnLockScreenVisibility() {
+        `when`(controller.available).thenReturn(true)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+            .thenReturn(1)
+        val component = setupComponent(true)
+
+        assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
+    }
+
+    @Test
+    fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
+        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
+            .thenReturn(0)
+        `when`(controller.available).thenReturn(true)
+        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
+            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
+        val component = setupComponent(true)
+
+        assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
+    }
+
+    private fun setupComponent(enabled: Boolean): ControlsComponent {
+        return ControlsComponent(
+            enabled,
+            mContext,
+            Lazy { controller },
+            Lazy { uiController },
+            Lazy { listingController },
+            lockPatternUtils,
+            keyguardStateController,
+            userTracker,
+            secureSettings
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 069699c..6d8c372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -173,4 +173,30 @@
         mDozeUi.transitionTo(UNINITIALIZED, DOZE);
         verify(mHost).setAnimateWakeup(eq(false));
     }
+
+    @Test
+    public void controlScreenOffTrueWhenKeyguardNotShowingAndControlUnlockedScreenOff() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+        // Tell doze that keyguard is not visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+        // Since we're controlling the unlocked screen off animation, verify that we've asked to
+        // control the screen off animation despite being unlocked.
+        verify(mDozeParameters).setControlScreenOffAnimation(true);
+    }
+
+    @Test
+    public void controlScreenOffFalseWhenKeyguardNotShowingAndControlUnlockedScreenOffFalse() {
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+
+        // Tell doze that keyguard is not visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false /* showing */);
+
+        // Since we're not controlling the unlocked screen off animation, verify that we haven't
+        // asked to control the screen off animation since we're unlocked.
+        verify(mDozeParameters).setControlScreenOffAnimation(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
index a52a598..0457100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 78ee593..1062fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -74,12 +74,14 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -135,6 +137,8 @@
     @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
     @Mock private Handler mHandler;
     @Mock private UserContextProvider mUserContextProvider;
+    @Mock private UserTracker mUserTracker;
+    @Mock private SecureSettings mSecureSettings;
     private ControlsComponent mControlsComponent;
 
     private TestableLooper mTestableLooper;
@@ -149,9 +153,14 @@
         when(mUserContextProvider.getUserContext()).thenReturn(mContext);
         mControlsComponent = new ControlsComponent(
                 true,
+                mContext,
                 () -> mControlsController,
                 () -> mControlsUiController,
-                () -> mControlsListingController
+                () -> mControlsListingController,
+                mLockPatternUtils,
+                mKeyguardStateController,
+                mUserTracker,
+                mSecureSettings
         );
 
         mGlobalActionsDialog = new GlobalActionsDialog(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 00943bc..b8c37fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -27,13 +29,17 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
 
+import com.android.systemui.R;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -45,6 +51,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -117,4 +124,20 @@
         mViewMediator.mViewMediatorCallback.keyguardGone();
         verify(mStatusBarKeyguardViewManager).setKeyguardGoingAwayState(eq(false));
     }
+
+    @Test
+    public void testIsAnimatingScreenOff() {
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+
+        mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
+        mViewMediator.setDozing(true);
+
+        // Mid-doze, we should be animating the screen off animation.
+        mViewMediator.onDozeAmountChanged(0.5f, 0.5f);
+        assertTrue(mViewMediator.isAnimatingScreenOff());
+
+        // Once we're 100% dozed, the screen off animation should be completed.
+        mViewMediator.onDozeAmountChanged(1f, 1f);
+        assertFalse(mViewMediator.isAnimatingScreenOff());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 2db224f..e88c728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -315,10 +315,9 @@
         }
         mediaDataManager.onNotificationAdded(KEY, notif)
 
-        // THEN it loads and uses the default background color
+        // THEN it still loads
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor))
-        assertThat(mediaDataCaptor.value!!.backgroundColor).isEqualTo(DEFAULT_COLOR)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
new file mode 100644
index 0000000..b3ad6ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.PeopleProviderUtils;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class PeopleProviderTest extends SysuiTestCase {
+    private static final String TAG = "PeopleProviderTest";
+
+    private static final Uri URI = Uri.parse(PeopleProviderUtils.PEOPLE_PROVIDER_SCHEME
+            + PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY);
+
+    private static final String SHORTCUT_ID_A = "shortcut_id_a";
+    private static final String PACKAGE_NAME_A = "package_name_a";
+    private static final UserHandle USER_HANDLE_A = UserHandle.of(1);
+    private static final String USERNAME = "username";
+
+    private final ShortcutInfo mShortcutInfo =
+            new ShortcutInfo.Builder(mContext, SHORTCUT_ID_A).setLongLabel(USERNAME).build();
+    private final ConversationChannel mConversationChannel =
+            new ConversationChannel(mShortcutInfo, USER_HANDLE_A.getIdentifier(),
+                    null, null, 0L, false);
+
+    private Bundle mExtras = new Bundle();
+
+    @Mock
+    private LauncherApps mLauncherApps;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private IPeopleManager mPeopleManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext.setMockPackageManager(mPackageManager);
+
+        PeopleProviderTestable provider = new PeopleProviderTestable();
+        provider.initializeForTesting(
+                mContext, PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY);
+        provider.setLauncherApps(mLauncherApps);
+        provider.setPeopleManager(mPeopleManager);
+        mContext.getContentResolver().addProvider(
+                PeopleProviderUtils.PEOPLE_PROVIDER_AUTHORITY, provider);
+
+        mContext.getTestablePermissions().setPermission(
+                PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+                PackageManager.PERMISSION_GRANTED);
+
+        when(mPeopleManager.getConversation(
+                eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A)))
+                .thenReturn(mConversationChannel);
+
+        mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_SHORTCUT_ID, SHORTCUT_ID_A);
+        mExtras.putString(PeopleProviderUtils.EXTRAS_KEY_PACKAGE_NAME, PACKAGE_NAME_A);
+        mExtras.putParcelable(PeopleProviderUtils.EXTRAS_KEY_USER_HANDLE, USER_HANDLE_A);
+    }
+
+    @Test
+    public void testPermissionDeniedThrowsSecurityException() throws RemoteException {
+        mContext.getTestablePermissions().setPermission(
+                PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_PERMISSION,
+                PackageManager.PERMISSION_DENIED);
+        try {
+            Bundle result = mContext.getContentResolver()
+                    .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null);
+            Assert.fail("Call should have failed with SecurityException");
+        } catch (SecurityException e) {
+        } catch (Exception e) {
+            Assert.fail("Call should have failed with SecurityException");
+        }
+    }
+
+    @Test
+    public void testPermissionGrantedNoExtraReturnsNull() throws RemoteException {
+        try {
+            Bundle result = mContext.getContentResolver()
+                    .call(URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, null);
+            Assert.fail("Call should have failed with IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        } catch (Exception e) {
+            Assert.fail("Call should have failed with IllegalArgumentException");
+        }
+    }
+
+    @Test
+    public void testPermissionGrantedExtrasReturnsRemoteViews() throws RemoteException {
+        try {
+            Bundle result = mContext.getContentResolver().call(
+                    URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras);
+            RemoteViews views = result.getParcelable(
+                    PeopleProviderUtils.RESPONSE_KEY_REMOTE_VIEWS);
+            assertThat(views).isNotNull();
+        } catch (Exception e) {
+            Assert.fail("Fail " + e);
+        }
+    }
+
+    @Test
+    public void testPermissionGrantedNoConversationForShortcutReturnsNull() throws RemoteException {
+        when(mPeopleManager.getConversation(
+                eq(PACKAGE_NAME_A), eq(USER_HANDLE_A.getIdentifier()), eq(SHORTCUT_ID_A)))
+                .thenReturn(null);
+        try {
+            Bundle result = mContext.getContentResolver().call(
+                    URI, PeopleProviderUtils.GET_PEOPLE_TILE_PREVIEW_METHOD, null, mExtras);
+            assertThat(result).isNull();
+        } catch (Exception e) {
+            Assert.fail("Fail " + e);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
new file mode 100644
index 0000000..ac18934
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.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.systemui.people;
+
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ProviderInfo;
+
+public class PeopleProviderTestable extends PeopleProvider {
+
+    public void initializeForTesting(Context context, String authority) {
+        ProviderInfo info = new ProviderInfo();
+        info.authority = authority;
+
+        attachInfoForTesting(context, info);
+    }
+
+    void setLauncherApps(LauncherApps launcherApps) {
+        mLauncherApps = launcherApps;
+    }
+
+    void setPeopleManager(IPeopleManager peopleManager) {
+        mPeopleManager = peopleManager;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index 6db21f9..4ee2759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.people;
 
 import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -52,6 +53,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
@@ -64,6 +66,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,10 +87,17 @@
 
     private static final int WIDGET_ID_WITH_SHORTCUT = 1;
     private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
-    private static final String SHORTCUT_ID = "101";
+    private static final String SHORTCUT_ID_1 = "101";
+    private static final String SHORTCUT_ID_2 = "202";
+    private static final String SHORTCUT_ID_3 = "303";
+    private static final String SHORTCUT_ID_4 = "404";
     private static final String NOTIFICATION_KEY = "notification_key";
     private static final String NOTIFICATION_CONTENT = "notification_content";
     private static final String TEST_LOOKUP_KEY = "lookup_key";
+    private static final String NOTIFICATION_TEXT_1 = "notification_text_1";
+    private static final String NOTIFICATION_TEXT_2 = "notification_text_2";
+    private static final String NOTIFICATION_TEXT_3 = "notification_text_3";
+    private static final String NOTIFICATION_TEXT_4 = "notification_text_4";
     private static final int TEST_COLUMN_INDEX = 1;
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
@@ -97,20 +109,66 @@
             .build();
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
-                    .Builder(SHORTCUT_ID, "username", ICON, new Intent())
+                    .Builder(SHORTCUT_ID_1, "username", ICON, new Intent())
                     .setNotificationKey(NOTIFICATION_KEY)
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
 
     private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
-            SHORTCUT_ID).setLongLabel(
+            SHORTCUT_ID_1).setLongLabel(
             "name").setPerson(PERSON)
             .build();
     private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext,
-            SHORTCUT_ID).setLongLabel(
+            SHORTCUT_ID_1).setLongLabel(
             "name")
             .build();
+    private final Notification mNotification1 = new Notification.Builder(mContext, "test")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("TEST_TEXT")
+            .setShortcutId(SHORTCUT_ID_1)
+            .setStyle(new Notification.MessagingStyle(PERSON)
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_1, 0, PERSON))
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_2, 20, PERSON))
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_3, 10, PERSON))
+            )
+            .build();
+    private final Notification mNotification2 = new Notification.Builder(mContext, "test2")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("OTHER_TEXT")
+            .setShortcutId(SHORTCUT_ID_2)
+            .setStyle(new Notification.MessagingStyle(PERSON)
+                    .addMessage(new Notification.MessagingStyle.Message(
+                            NOTIFICATION_TEXT_4, 0, PERSON))
+            )
+            .build();
+    private final Notification mNotification3 = new Notification.Builder(mContext, "test2")
+            .setContentTitle("TEST_TITLE")
+            .setContentText("OTHER_TEXT")
+            .setShortcutId(SHORTCUT_ID_3)
+            .setStyle(new Notification.MessagingStyle(PERSON))
+            .build();
+    private final NotificationEntry mNotificationEntry1 = new NotificationEntryBuilder()
+            .setNotification(mNotification1)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_1).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
+    private final NotificationEntry mNotificationEntry2 = new NotificationEntryBuilder()
+            .setNotification(mNotification2)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_2).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
+    private final NotificationEntry mNotificationEntry3 = new NotificationEntryBuilder()
+            .setNotification(mNotification3)
+            .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_3).build())
+            .setUser(UserHandle.of(0))
+            .setPkg(PACKAGE_NAME)
+            .build();
 
     @Mock
     private NotificationListener mListenerService;
@@ -130,6 +188,8 @@
     private ContentResolver mMockContentResolver;
     @Mock
     private Context mMockContext;
+    @Mock
+    private NotificationEntryManager mNotificationEntryManager;
 
     @Before
     public void setUp() throws RemoteException {
@@ -152,15 +212,17 @@
                 isNull())).thenReturn(mMockCursor);
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
+        when(mNotificationEntryManager.getVisibleNotifications())
+                .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3));
     }
 
     @Test
     public void testGetTilesReturnsSortedListWithMultipleRecentConversations() throws Exception {
         // Ensure the less-recent Important conversation is before more recent conversations.
         ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID, false, 3);
+                SHORTCUT_ID_1, false, 3);
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 1,
+                SHORTCUT_ID_1 + 1,
                 true, 1);
         when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
@@ -169,9 +231,9 @@
         // Ensure the non-Important conversation is sorted between these recent conversations.
         ConversationChannel recentConversationBeforeNonImportantConversation =
                 getConversationChannel(
-                        SHORTCUT_ID + 2, 4);
+                        SHORTCUT_ID_1 + 2, 4);
         ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID + 3,
+                getConversationChannel(SHORTCUT_ID_1 + 3,
                         2);
         when(mPeopleManager.getRecentConversations()).thenReturn(
                 new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -179,7 +241,8 @@
 
         List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
                 mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+                mLauncherApps, mNotificationEntryManager)
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
 
         assertThat(orderedShortcutIds).containsExactly(
                 // Even though the oldest conversation, should be first since "important"
@@ -196,11 +259,11 @@
             throws Exception {
         // Ensure the less-recent Important conversation is before more recent conversations.
         ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID, false, 3);
+                SHORTCUT_ID_1, false, 3);
         ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 1, true, 3);
+                SHORTCUT_ID_1 + 1, true, 3);
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
-                SHORTCUT_ID + 2,
+                SHORTCUT_ID_1 + 2,
                 true, 1);
         when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
@@ -210,9 +273,9 @@
         // Ensure the non-Important conversation is sorted between these recent conversations.
         ConversationChannel recentConversationBeforeNonImportantConversation =
                 getConversationChannel(
-                        SHORTCUT_ID + 3, 4);
+                        SHORTCUT_ID_1 + 3, 4);
         ConversationChannel recentConversationAfterNonImportantConversation =
-                getConversationChannel(SHORTCUT_ID + 4,
+                getConversationChannel(SHORTCUT_ID_1 + 4,
                         2);
         when(mPeopleManager.getRecentConversations()).thenReturn(
                 new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation,
@@ -220,7 +283,8 @@
 
         List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles(
                 mContext, mNotificationManager, mPeopleManager,
-                mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList());
+                mLauncherApps, mNotificationEntryManager)
+                .stream().map(tile -> tile.getId()).collect(Collectors.toList());
 
         assertThat(orderedShortcutIds).containsExactly(
                 // Important conversations should be sorted at the beginning.
@@ -238,7 +302,7 @@
         Notification notification = new Notification.Builder(mContext, "test")
                 .setContentTitle("TEST_TITLE")
                 .setContentText("TEST_TEXT")
-                .setShortcutId(SHORTCUT_ID)
+                .setShortcutId(SHORTCUT_ID_1)
                 .build();
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(notification)
@@ -341,24 +405,126 @@
 
     @Test
     public void testGetLastMessagingStyleMessage() {
-        Notification notification = new Notification.Builder(mContext, "test")
-                .setContentTitle("TEST_TITLE")
-                .setContentText("TEST_TEXT")
-                .setShortcutId(SHORTCUT_ID)
-                .setStyle(new Notification.MessagingStyle(PERSON)
-                        .addMessage(new Notification.MessagingStyle.Message("text1", 0, PERSON))
-                        .addMessage(new Notification.MessagingStyle.Message("text2", 20, PERSON))
-                        .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON))
-                )
-                .build();
         StatusBarNotification sbn = new SbnBuilder()
-                .setNotification(notification)
+                .setNotification(mNotification1)
                 .build();
 
         Notification.MessagingStyle.Message lastMessage =
                 PeopleSpaceUtils.getLastMessagingStyleMessage(sbn);
 
-        assertThat(lastMessage.getText()).isEqualTo("text2");
+        assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromNotification() {
+        StatusBarNotification sbn = new SbnBuilder()
+                .setNotification(mNotification1)
+                .build();
+
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromNotification(tile, sbn);
+
+        assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromNotificationNoContent() {
+        StatusBarNotification sbn = new SbnBuilder()
+                .setNotification(mNotification3)
+                .build();
+
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromNotification(tile, sbn);
+
+        assertThat(actual.getNotificationKey()).isEqualTo(null);
+        assertThat(actual.getNotificationContent()).isEqualTo(null);
+    }
+
+    @Test
+    public void testAugmentTileFromVisibleNotifications() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromVisibleNotifications(tile,
+                        Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+        assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
+    }
+
+    @Test
+    public void testAugmentTileFromVisibleNotificationsDifferentShortcutId() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        PeopleSpaceTile actual = PeopleSpaceUtils
+                .augmentTileFromVisibleNotifications(tile,
+                        Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1));
+
+        assertThat(actual.getNotificationContent()).isEqualTo(null);
+    }
+
+    @Test
+    public void testAugmentTilesFromVisibleNotificationsSingleTile() {
+        PeopleSpaceTile tile =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+                .augmentTilesFromVisibleNotifications(List.of(tile), mNotificationEntryManager);
+
+        assertThat(actualList.size()).isEqualTo(1);
+        assertThat(actualList.get(0).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_2);
+
+        verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
+    }
+
+    @Test
+    public void testAugmentTilesFromVisibleNotificationsMultipleTiles() {
+        PeopleSpaceTile tile1 =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(1)
+                        .build();
+        PeopleSpaceTile tile2 =
+                new PeopleSpaceTile
+                        .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent())
+                        .setPackageName(PACKAGE_NAME)
+                        .setUid(0)
+                        .build();
+        List<PeopleSpaceTile> actualList = PeopleSpaceUtils
+                .augmentTilesFromVisibleNotifications(List.of(tile1, tile2),
+                        mNotificationEntryManager);
+
+        assertThat(actualList.size()).isEqualTo(2);
+        assertThat(actualList.get(0).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_2);
+        assertThat(actualList.get(1).getNotificationContent().toString())
+                .isEqualTo(NOTIFICATION_TEXT_4);
+
+        verify(mNotificationEntryManager, times(1)).getVisibleNotifications();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index ef314ad..9470141 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -421,6 +421,7 @@
             throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
 
         Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext)
                 .setContentTitle("TEST_TITLE")
@@ -429,15 +430,21 @@
                 .build();
         StatusBarNotification sbn = new SbnBuilder()
                 .setNotification(notificationWithoutMessagingStyle)
+                .setPkg(TEST_PACKAGE_A)
+                .setUid(0)
                 .build();
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
-        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle options = requireNonNull(mBundleArgumentCaptor.getValue());
+        assertThat((PeopleSpaceTile) options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE))
+                .isEqualTo(PERSON_TILE);
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 0dc268a..fb817ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,6 +30,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
@@ -37,6 +41,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHost;
@@ -44,6 +49,7 @@
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.util.animation.DisappearParameters;
 
 import org.junit.Before;
@@ -86,17 +92,25 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
+    @Mock
+    Resources mResources;
+    @Mock
+    Configuration mConfiguration;
 
     private QSPanelControllerBase<QSPanel> mController;
 
+
+
     /** Implementation needed to ensure we have a reflectively-available class name. */
     private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
                 QSCustomizerController qsCustomizerController, MediaHost mediaHost,
                 MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-                DumpManager dumpManager) {
+                DumpManager dumpManager, FeatureFlags featureFlags) {
             super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
-                    qsLogger, dumpManager);
+                    qsLogger, dumpManager, featureFlags);
         }
 
         @Override
@@ -121,10 +135,12 @@
         when(mQSTileRevealControllerFactory.create(any(), any()))
                 .thenReturn(mQSTileRevealController);
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
+        when(mQSPanel.getResources()).thenReturn(mResources);
+        when(mResources.getConfiguration()).thenReturn(mConfiguration);
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
 
         mController.init();
         reset(mQSTileRevealController);
@@ -136,7 +152,7 @@
 
         QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
                 mQSTileHost, mQSCustomizerController, mMediaHost,
-                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
+                mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
             @Override
             protected QSTileRevealController createTileRevealController() {
                 return mQSTileRevealController;
@@ -218,4 +234,18 @@
         verify(mQSLogger).logAllTilesChangeListening(false, "QSPanel", "dnd");
         verify(mPagedTileLayout).setListening(false, mUiEventLogger);
     }
+
+
+    @Test
+    public void testShouldUzeHorizontalLayout_falseForSplitShade() {
+        mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        when(mMediaHost.getVisible()).thenReturn(true);
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 4381158..0dfebab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.settings.brightness.BrightnessController;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.animation.DisappearParameters;
 
@@ -93,6 +94,8 @@
     QSTileView mQSTileView;
     @Mock
     PagedTileLayout mPagedTileLayout;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     private QSPanelController mController;
 
@@ -118,7 +121,9 @@
                 mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                /* labelsFlag */ false);
+                /* labelsFlag */ false,
+                mFeatureFlags
+        );
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 107160f..5870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.qs.QSTileView
 import com.android.systemui.qs.customize.QSCustomizerController
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.FeatureFlags
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -63,6 +64,8 @@
     private lateinit var tileLayout: TileLayout
     @Mock
     private lateinit var tileView: QSTileView
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
     private lateinit var controller: QuickQSPanelController
 
@@ -84,7 +87,8 @@
                 uiEventLogger,
                 qsLogger,
                 dumpManager,
-                false
+                false,
+                featureFlags
         )
 
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index b452d3a..e855834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -33,7 +33,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.CarrierTextManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -54,7 +54,7 @@
 
     private QSCarrierGroupController mQSCarrierGroupController;
     private NetworkController.SignalCallback mSignalCallback;
-    private CarrierTextController.CarrierTextCallback mCallback;
+    private CarrierTextManager.CarrierTextCallback mCallback;
     @Mock
     private QSCarrierGroup mQSCarrierGroup;
     @Mock
@@ -62,9 +62,9 @@
     @Mock
     private NetworkController mNetworkController;
     @Mock
-    private CarrierTextController.Builder mCarrierTextControllerBuilder;
+    private CarrierTextManager.Builder mCarrierTextControllerBuilder;
     @Mock
-    private CarrierTextController mCarrierTextController;
+    private CarrierTextManager mCarrierTextManager;
     private TestableLooper mTestableLooper;
 
     @Before
@@ -83,11 +83,11 @@
                 .thenReturn(mCarrierTextControllerBuilder);
         when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
                 .thenReturn(mCarrierTextControllerBuilder);
-        when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextController);
+        when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
 
         doAnswer(invocation -> mCallback = invocation.getArgument(0))
-                .when(mCarrierTextController)
-                .setListening(any(CarrierTextController.CarrierTextCallback.class));
+                .when(mCarrierTextManager)
+                .setListening(any(CarrierTextManager.CarrierTextCallback.class));
 
         when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
         when(mQSCarrierGroup.getCarrier1View()).thenReturn(mock(QSCarrier.class));
@@ -98,7 +98,7 @@
 
         mQSCarrierGroupController = new QSCarrierGroupController.Builder(
                 mActivityStarter, handler, TestableLooper.get(this).getLooper(),
-                mNetworkController, mCarrierTextControllerBuilder)
+                mNetworkController, mCarrierTextControllerBuilder, mContext)
                 .setQSCarrierGroup(mQSCarrierGroup)
                 .build();
 
@@ -113,8 +113,8 @@
                 (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c1 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c1 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 false,
@@ -122,8 +122,8 @@
         mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c2 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c2 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
@@ -131,8 +131,8 @@
         mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c3 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c3 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 false,
@@ -140,8 +140,8 @@
         mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -159,8 +159,8 @@
                 (Answer<Integer>) invocationOnMock -> invocationOnMock.getArgument(0));
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c1 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c1 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 false,
@@ -168,8 +168,8 @@
         mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c2 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c2 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -177,8 +177,8 @@
         mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims false
-        CarrierTextController.CarrierTextCallbackInfo
-                c3 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c3 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 false,
@@ -186,8 +186,8 @@
         mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims true
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
@@ -203,8 +203,8 @@
         when(spiedCarrierGroupController.getSlotIndex(anyInt())).thenReturn(
                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
 
-        CarrierTextController.CarrierTextCallbackInfo
-                c4 = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                c4 = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{"", ""},
                 true,
@@ -223,8 +223,8 @@
 
     @Test
     public void testNoEmptyVisibleView_airplaneMode() {
-        CarrierTextController.CarrierTextCallbackInfo
-                info = new CarrierTextController.CarrierTextCallbackInfo(
+        CarrierTextManager.CarrierTextCallbackInfo
+                info = new CarrierTextManager.CarrierTextCallbackInfo(
                 "",
                 new CharSequence[]{""},
                 true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
new file mode 100644
index 0000000..d3dbe2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class DeviceControlsTileTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var qsHost: QSHost
+    @Mock
+    private lateinit var metricsLogger: MetricsLogger
+    @Mock
+    private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var qsLogger: QSLogger
+    private lateinit var controlsComponent: ControlsComponent
+    @Mock
+    private lateinit var controlsUiController: ControlsUiController
+    @Mock
+    private lateinit var controlsListingController: ControlsListingController
+    @Mock
+    private lateinit var controlsController: ControlsController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var controlsDialog: ControlsDialog
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
+    private lateinit var serviceInfo: ControlsServiceInfo
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Captor
+    private lateinit var listingCallbackCaptor:
+            ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var tile: DeviceControlsTile
+
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        `when`(qsHost.context).thenReturn(mContext)
+        `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+
+        controlsComponent = ControlsComponent(
+                true,
+                mContext,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController },
+                lockPatternUtils,
+                keyguardStateController,
+                userTracker,
+                secureSettings
+        )
+
+        globalSettings = FakeSettings()
+
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 1)
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(true)
+
+        `when`(userTracker.userHandle.identifier).thenReturn(0)
+
+        tile = createTile()
+    }
+
+    @Test
+    fun testAvailable() {
+        `when`(controlsController.available).thenReturn(true)
+        assertThat(tile.isAvailable).isTrue()
+    }
+
+    @Test
+    fun testNotAvailableFeature() {
+        `when`(controlsController.available).thenReturn(true)
+        `when`(featureFlags.isKeyguardLayoutEnabled).thenReturn(false)
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableControls() {
+        controlsComponent = ControlsComponent(
+                false,
+                mContext,
+                { controlsController },
+                { controlsUiController },
+                { controlsListingController },
+                lockPatternUtils,
+                keyguardStateController,
+                userTracker,
+                secureSettings
+        )
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testNotAvailableFlag() {
+        globalSettings.putInt(DeviceControlsTile.SETTINGS_FLAG, 0)
+        tile = createTile()
+
+        assertThat(tile.isAvailable).isFalse()
+    }
+
+    @Test
+    fun testObservingCallback() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                any(ControlsListingController.ControlsListingCallback::class.java)
+        )
+    }
+
+    @Test
+    fun testLongClickIntent() {
+        assertThat(tile.longClickIntent.action).isEqualTo(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
+    }
+
+    @Test
+    fun testUnavailableByDefault() {
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateUnavailableIfNoListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testStateAvailableIfListings() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+    }
+
+    @Test
+    fun testMoveBetweenStates() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        listingCallbackCaptor.value.onServicesUpdated(emptyList())
+        testableLooper.processAllMessages()
+
+        assertThat(tile.state.state).isEqualTo(Tile.STATE_UNAVAILABLE)
+    }
+
+    @Test
+    fun testNoDialogWhenUnavailable() {
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog, never()).show(any(ControlsUiController::class.java))
+    }
+
+    @Test
+    fun testDialogShowWhenAvailable() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        verify(controlsDialog).show(controlsUiController)
+    }
+
+    @Test
+    fun testDialogDismissedOnDestroy() {
+        verify(controlsListingController).observe(
+                any(LifecycleOwner::class.java),
+                capture(listingCallbackCaptor)
+        )
+
+        listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+        testableLooper.processAllMessages()
+
+        tile.click()
+        testableLooper.processAllMessages()
+
+        tile.destroy()
+        testableLooper.processAllMessages()
+        verify(controlsDialog).dismiss()
+    }
+
+    private fun createTile(): DeviceControlsTile {
+        return DeviceControlsTile(
+                qsHost,
+                testableLooper.looper,
+                Handler(testableLooper.looper),
+                metricsLogger,
+                statusBarStateController,
+                activityStarter,
+                qsLogger,
+                controlsComponent,
+                featureFlags,
+                { controlsDialog },
+                globalSettings
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index c1c6371..580f800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -57,6 +57,8 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class ScrollCaptureClientTest extends SysuiTestCase {
+    private static final float MAX_PAGES = 3f;
+
     private Context mContext;
     private IWindowManager mWm;
 
@@ -96,7 +98,7 @@
 
         Connection conn = mConnectionConsumer.getValue();
 
-        conn.start(mSessionConsumer);
+        conn.start(mSessionConsumer, MAX_PAGES);
         verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
 
         Session session = mSessionConsumer.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
index bd37259..4c84df2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java
@@ -34,7 +34,7 @@
         linearLayout.setOrientation(LinearLayout.VERTICAL);
         TextView text = new TextView(this);
         text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40);
-        text.setText(com.android.systemui.R.string.test_content);
+        text.setText(com.android.systemui.tests.R.string.test_content);
         linearLayout.addView(text);
         scrollView.addView(linearLayout);
         setContentView(scrollView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index d131dce..e16d4d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -36,16 +36,20 @@
 
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.os.Handler;
 import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Pair;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.DeviceConfigProxyFake;
 
 import junit.framework.Assert;
 
@@ -55,38 +59,44 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class AssistantFeedbackControllerTest extends SysuiTestCase {
-    private static final int ON = 1;
-    private static final int OFF = 0;
     private static final String TEST_PACKAGE_NAME = "test_package";
     private static final int TEST_UID = 1;
 
     private AssistantFeedbackController mAssistantFeedbackController;
+    private DeviceConfigProxyFake mProxyFake;
+    private TestableLooper mTestableLooper;
+
     private StatusBarNotification mSbn;
 
     @Before
     public void setUp() {
-        mAssistantFeedbackController = new AssistantFeedbackController(mContext);
-        switchSetting(ON);
+        mProxyFake = new DeviceConfigProxyFake();
+        mTestableLooper = TestableLooper.get(this);
+        mAssistantFeedbackController = new AssistantFeedbackController(
+                new Handler(mTestableLooper.getLooper()),
+                mContext, mProxyFake);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
                 0, null, TEST_UID, 0, new Notification(),
                 UserHandle.CURRENT, null, 0);
     }
 
     @Test
-    public void testUserControls_settingDisabled() {
-        switchSetting(OFF);
+    public void testFlagDisabled() {
+        switchFlag("false");
         assertFalse(mAssistantFeedbackController.isFeedbackEnabled());
     }
 
     @Test
-    public void testUserControls_settingEnabled() {
+    public void testFlagEnabled() {
+        switchFlag("true");
         assertTrue(mAssistantFeedbackController.isFeedbackEnabled());
     }
 
     @Test
-    public void testFeedback_settingDisabled() {
-        switchSetting(OFF);
+    public void testFeedback_flagDisabled() {
+        switchFlag("false");
         assertEquals(STATUS_UNCHANGED, mAssistantFeedbackController.getFeedbackStatus(
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
         assertFalse(mAssistantFeedbackController.showFeedbackIndicator(
@@ -95,6 +105,7 @@
 
     @Test
     public void testFeedback_changedImportance() {
+        switchFlag("true");
         NotificationEntry entry = getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, RANKING_UNCHANGED);
         assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
         assertTrue(mAssistantFeedbackController.showFeedbackIndicator(entry));
@@ -110,6 +121,7 @@
 
     @Test
     public void testFeedback_changedRanking() {
+        switchFlag("true");
         NotificationEntry entry =
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_PROMOTED);
         assertEquals(STATUS_PROMOTED, mAssistantFeedbackController.getFeedbackStatus(entry));
@@ -121,8 +133,8 @@
     }
 
     @Test
-    public void testGetFeedbackResources_settingDisabled() {
-        switchSetting(OFF);
+    public void testGetFeedbackResources_flagDisabled() {
+        switchFlag("false");
         Assert.assertEquals(new Pair(0, 0), mAssistantFeedbackController.getFeedbackResources(
                 getEntry(IMPORTANCE_DEFAULT, IMPORTANCE_DEFAULT, RANKING_UNCHANGED)));
     }
@@ -138,9 +150,10 @@
                 .build();
     }
 
-    private void switchSetting(int setting) {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.NOTIFICATION_FEEDBACK_ENABLED, setting);
-        mAssistantFeedbackController.update(null);
+    private void switchFlag(String enabled) {
+        mProxyFake.setProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK,
+                enabled, false);
+        mTestableLooper.processAllMessages();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
 import androidx.palette.graphics.Palette;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6b0a23f..2e2945e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -50,7 +50,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -61,6 +60,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
 import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 6fcc7fa..64a7bee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -43,7 +43,6 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
-import com.android.systemui.R;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.media.MediaFeatureFlag;
@@ -68,6 +67,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.tests.R;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.systemui.wmshell.BubblesTestActivity;
 import com.android.wm.shell.bubbles.Bubbles;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index a147c8d..45f7c5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index b9fd75e..421c6f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.reset;
@@ -114,4 +116,37 @@
 
         assertThat(mDozeParameters.getAlwaysOn()).isFalse();
     }
+
+    @Test
+    public void testControlUnlockedScreenOffAnimation_dozeAfterScreenOff_false() {
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+        when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(true);
+
+        assertTrue(mDozeParameters.shouldControlUnlockedScreenOff());
+
+        // Trigger the setter for the current value.
+        mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+        // We should have asked power manager not to doze after screen off no matter what, since
+        // we're animating and controlling screen off.
+        verify(mPowerManager).setDozeAfterScreenOff(eq(false));
+    }
+
+    @Test
+    public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() {
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+        when(mFeatureFlags.useNewLockscreenAnimations()).thenReturn(false);
+
+        assertFalse(mDozeParameters.shouldControlUnlockedScreenOff());
+
+        // Trigger the setter for the current value.
+        mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
+
+        // We should have asked power manager to doze only if we're not controlling screen off
+        // normally.
+        verify(mPowerManager).setDozeAfterScreenOff(
+                eq(!mDozeParameters.shouldControlScreenOff()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ee1d758..9b623f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -393,10 +393,11 @@
 
     private void positionClock() {
         mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
-                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
+                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
+                0 /* userSwitchHeight */, mPreferredClockY, 0 /* userSwitchPreferredY */,
                 mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
-                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */,
-                mQsExpansion, mCutoutTopInset);
+                0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion,
+                mCutoutTopInset);
         mClockPositionAlgorithm.run(mClockPosition);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c07ba72..d69d2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -37,6 +37,7 @@
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.os.PowerManager;
+import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
@@ -47,6 +48,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
@@ -57,7 +60,10 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardStatusViewController;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
@@ -193,12 +199,24 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
+    private KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
+    @Mock
+    private KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
+    @Mock
+    private QSDetailDisplayer mQSDetailDisplayer;
+    @Mock
     private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
     @Mock
+    private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
+    @Mock
+    private KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
+    @Mock
     private KeyguardClockSwitchController mKeyguardClockSwitchController;
     @Mock
     private KeyguardStatusViewController mKeyguardStatusViewController;
     @Mock
+    private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
+    @Mock
     private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     @Mock
     private AuthController mAuthController;
@@ -213,11 +231,13 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
-    private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    @Mock
     private AmbientState mAmbientState;
+    @Mock
+    private UserManager mUserManager;
+
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
+    private NotificationsQuickSettingsContainer mNotificationContainerParent;
 
     @Before
     public void setup() {
@@ -250,6 +270,7 @@
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
         when(mView.findViewById(R.id.notification_container_parent))
                 .thenReturn(mNotificationContainerParent);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
@@ -283,6 +304,10 @@
                 new ViewGroup.LayoutParams(600, 400));
         when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn(
                 new ViewGroup.LayoutParams(600, 400));
+        when(mKeyguardStatusBarViewComponentFactory.build(any()))
+                .thenReturn(mKeyguardStatusBarViewComponent);
+        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
+                .thenReturn(mKeyguardStatusBarViewController);
 
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
@@ -299,11 +324,15 @@
                 mBiometricUnlockController, mStatusBarKeyguardViewManager,
                 mNotificationStackScrollLayoutController,
                 mKeyguardStatusViewComponentFactory,
+                mKeyguardQsUserSwitchComponentFactory,
+                mKeyguardUserSwitcherComponentFactory,
+                mKeyguardStatusBarViewComponentFactory,
+                mQSDetailDisplayer,
                 mGroupManager,
                 mNotificationAreaController,
                 mAuthController,
-                new QSDetailDisplayer(),
                 mScrimController,
+                mUserManager,
                 mMediaDataManager,
                 mAmbientState,
                 mFeatureFlags,
@@ -425,16 +454,11 @@
 
     @Test
     public void testAllChildrenOfNotificationContainer_haveIds() {
-        when(mNotificationContainerParent.getChildCount()).thenReturn(2);
         when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
         when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
 
-        View view1 = new View(mContext);
-        view1.setId(1);
-        when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1);
-
-        View view2 = mock(View.class);
-        when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2);
+        mNotificationContainerParent.addView(newViewWithId(1));
+        mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
 
         mNotificationPanelViewController.updateResources();
 
@@ -442,6 +466,51 @@
         assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
     }
 
+    @Test
+    public void testSinglePaneShadeLayout_isAlignedToParent() {
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+        mNotificationPanelViewController.updateResources();
+
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+                R.id.notification_stack_scroller).layout;
+        assertThat(qsFrameLayout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID);
+        assertThat(stackScrollerLayout.startToStart).isEqualTo(ConstraintSet.PARENT_ID);
+    }
+
+    @Test
+    public void testSplitShadeLayout_isAlignedToGuideline() {
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
+        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
+
+        mNotificationPanelViewController.updateResources();
+
+        ConstraintSet constraintSet = new ConstraintSet();
+        constraintSet.clone(mNotificationContainerParent);
+        ConstraintSet.Layout qsFrameLayout = constraintSet.getConstraint(R.id.qs_frame).layout;
+        ConstraintSet.Layout stackScrollerLayout = constraintSet.getConstraint(
+                R.id.notification_stack_scroller).layout;
+        assertThat(qsFrameLayout.endToEnd).isEqualTo(R.id.qs_edge_guideline);
+        assertThat(stackScrollerLayout.startToStart).isEqualTo(R.id.qs_edge_guideline);
+    }
+
+    private View newViewWithId(int id) {
+        View view = new View(mContext);
+        view.setId(id);
+        ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.setLayoutParams(layoutParams);
+        return view;
+    }
+
     private void onTouchEvent(MotionEvent ev) {
         mTouchHandler.onTouch(mView, ev);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index c212cf3..67c1a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -26,12 +26,12 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.settingslib.R;
 import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tests.R;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
index fc1a791..e479882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt
@@ -60,9 +60,9 @@
     @Mock
     private lateinit var layoutInflater: LayoutInflater
     @Mock
-    private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher
+    private lateinit var keyguardUserSwitcherController: KeyguardUserSwitcherController
 
-    private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter
+    private lateinit var adapter: KeyguardUserSwitcherController.KeyguardUserAdapter
     private lateinit var picture: Bitmap
 
     @Before
@@ -72,8 +72,11 @@
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater)
         `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
                 .thenReturn(inflatedUserDetailItemView)
-        adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController,
-                keyguardUserSwitcher)
+        adapter = KeyguardUserSwitcherController.KeyguardUserAdapter(
+                mContext,
+                mContext.resources,
+                LayoutInflater.from(mContext),
+                userSwitcherController, keyguardUserSwitcherController)
         picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
     }
 
@@ -118,11 +121,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsSameType() {
         val v: UserDetailItemView? = createViewFromSameType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
@@ -150,11 +153,11 @@
     }
 
     @Test
-    fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
+    fun shouldSetOnOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() {
         val v: UserDetailItemView? = createViewFromDifferentType(
                 isCurrentUser = true, isGuestUser = false)
         assertNotNull(v)
-        verify(v)!!.setOnClickListener(null)
+        verify(v)!!.setOnClickListener(adapter)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index f8b6383..89cc2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -430,6 +430,10 @@
         updateSignalStrength();
     }
 
+    public void setImsType(int imsType) {
+        mMobileSignalController.setImsType(imsType);
+    }
+
     public void setIsGsm(boolean gsm) {
         when(mSignalStrength.isGsm()).thenReturn(gsm);
         updateSignalStrength();
@@ -632,6 +636,14 @@
         }
     }
 
+    protected void verifyLastCallStrength(int icon) {
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        verify(mCallbackHandler, Mockito.atLeastOnce()).setCallIndicator(
+                iconArg.capture(),
+                anyInt());
+        assertEquals("Call strength, in status bar", icon, (int) iconArg.getValue().icon);
+    }
+
     protected void assertNetworkNameEquals(String expected) {
        assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 10166cb..fc1a08ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -15,6 +15,7 @@
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.telephony.CellSignalStrength;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -243,6 +244,28 @@
         }
     }
 
+    @Test
+    public void testCallStrengh() {
+        String testSsid = "Test SSID";
+        setWifiEnabled(true);
+        setWifiState(true, testSsid);
+        // Set the ImsType to be IMS_TYPE_WLAN
+        setImsType(2);
+        setWifiLevel(1);
+        for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+            setWifiLevel(testLevel);
+            verifyLastCallStrength(TelephonyIcons.WIFI_CALL_STRENGTH_ICONS[testLevel]);
+        }
+        // Set the ImsType to be IMS_TYPE_WWAN
+        setImsType(1);
+        for (int testStrength = 0;
+                testStrength < CellSignalStrength.getNumSignalStrengthLevels(); testStrength++) {
+            setupDefaultSignal();
+            setLevel(testStrength);
+            verifyLastCallStrength(TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[testStrength]);
+        }
+    }
+
     protected void setWifiActivity(int activity) {
         // TODO: Not this, because this variable probably isn't sticking around.
         mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index edaff5f..45828c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -32,13 +32,18 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
 import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -70,11 +75,12 @@
     private static final String TEST_DISABLED_PREFIX = "com.example.";
     private static final String TEST_ENABLED_PREFIX = "com.example.enabled.";
 
-    private static final Map<String, String> ALL_CATEGORIES_MAP = Maps.newArrayMap();
+    private static final Map<String, OverlayIdentifier> ALL_CATEGORIES_MAP = Maps.newArrayMap();
 
     static {
         for (String category : THEME_CATEGORIES) {
-            ALL_CATEGORIES_MAP.put(category, TEST_DISABLED_PREFIX + category);
+            ALL_CATEGORIES_MAP.put(category,
+                    new OverlayIdentifier(TEST_DISABLED_PREFIX + category));
         }
     }
 
@@ -87,6 +93,8 @@
     OverlayManager mOverlayManager;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    OverlayManagerTransaction.Builder mTransactionBuilder;
 
     private ThemeOverlayApplier mManager;
 
@@ -94,7 +102,12 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
-                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager);
+                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+            @Override
+            protected OverlayManagerTransaction.Builder getTransactionBuilder() {
+                return mTransactionBuilder;
+            }
+        };
         when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
                 .thenReturn(Lists.newArrayList(
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
@@ -147,24 +160,26 @@
 
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
+        verify(mOverlayManager).commit(any());
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
     @Test
     public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
-        for (Map.Entry<String, String> entry : ALL_CATEGORIES_MAP.entrySet()) {
+        for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
-                verify(mOverlayManager).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder).setEnabled(eq(entry.getValue()), eq(true),
+                        eq(UserHandle.SYSTEM.getIdentifier()));
             } else {
-                verify(mOverlayManager, never()).setEnabledExclusiveInCategory(
-                        entry.getValue(), UserHandle.SYSTEM);
+                verify(mTransactionBuilder, never()).setEnabled(
+                        eq(entry.getValue()), eq(true), eq(UserHandle.SYSTEM.getIdentifier()));
             }
         }
     }
@@ -174,17 +189,34 @@
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         UserHandle newUserHandle = UserHandle.of(10);
         userHandles.add(newUserHandle);
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, userHandles);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles);
 
-        for (String overlayPackage : ALL_CATEGORIES_MAP.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, newUserHandle);
+        for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(newUserHandle.getIdentifier()));
+        }
+    }
+
+    @Test
+    public void applyCurrentUserOverlays_createsPendingOverlays() {
+        Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
+        UserHandle newUserHandle = UserHandle.of(10);
+        userHandles.add(newUserHandle);
+        FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] {
+                mock(FabricatedOverlay.class)
+        };
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles);
+
+        for (FabricatedOverlay overlay : pendingCreation) {
+            verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
         }
     }
 
     @Test
     public void allCategoriesSpecified_overlayManagerNotQueried() {
-        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager, never())
                 .getOverlayInfosForTarget(anyString(), any(UserHandle.class));
@@ -192,48 +224,56 @@
 
     @Test
     public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
-        for (String overlayPackage : categoryToPackage.values()) {
-            verify(mOverlayManager).setEnabledExclusiveInCategory(overlayPackage, TEST_USER);
+        for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
+            verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
+                    eq(TEST_USER.getIdentifier()));
         }
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS,
-                false, TEST_USER);
-        verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
-                false, TEST_USER);
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_SETTINGS)),
+                eq(false), eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder).setEnabled(
+                eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID)),
+                eq(false), eq(TEST_USER.getIdentifier()));
     }
 
     @Test
     public void zeroCategoriesSpecified_allDisabled() {
-        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES);
 
         for (String category : THEME_CATEGORIES) {
-            verify(mOverlayManager).setEnabled(TEST_ENABLED_PREFIX + category, false, TEST_USER);
+            verify(mTransactionBuilder).setEnabled(
+                    eq(new OverlayIdentifier(TEST_ENABLED_PREFIX + category)), eq(false),
+                    eq(TEST_USER.getIdentifier()));
         }
     }
 
     @Test
     public void nonThemeCategorySpecified_ignored() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
-        categoryToPackage.put("blah.category", "com.example.blah.category");
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
-        verify(mOverlayManager, never()).setEnabled("com.example.blah.category", false, TEST_USER);
-        verify(mOverlayManager, never()).setEnabledExclusiveInCategory("com.example.blah.category",
-                TEST_USER);
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
+                eq(TEST_USER.getIdentifier()));
+        verify(mTransactionBuilder, never()).setEnabled(
+                eq(new OverlayIdentifier("com.example.blah.category")), eq(true),
+                eq(TEST_USER.getIdentifier()));
     }
 
     @Test
     public void overlayManagerOnlyQueriedForUnspecifiedPackages() {
-        Map<String, String> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
+        Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP);
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS);
 
-        mManager.applyCurrentUserOverlays(categoryToPackage, TEST_USER_HANDLES);
+        mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES);
 
         verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM);
         verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE,
@@ -247,7 +287,8 @@
 
     private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName,
             String category, boolean enabled) {
-        return new OverlayInfo(packageName, targetPackageName, null, category, "",
-                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false);
+        return new OverlayInfo(packageName, null, targetPackageName, null, category, "",
+                enabled ? OverlayInfo.STATE_ENABLED : OverlayInfo.STATE_DISABLED, 0, 0, false,
+                false);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index d33fac0..f7f8d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.theme;
 
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_ACCENT_COLOR_PACKAGE;
-import static com.android.systemui.theme.ThemeOverlayApplier.MONET_SYSTEM_PALETTE_PACKAGE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayController.USE_LOCK_SCREEN_WALLPAPER;
@@ -27,12 +25,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayIdentifier;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -40,11 +41,13 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -56,7 +59,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
@@ -85,6 +87,8 @@
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback;
     @Captor
@@ -93,10 +97,20 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isMonetEnabled()).thenReturn(true);
         mThemeOverlayController = new ThemeOverlayController(null /* context */,
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mKeyguardStateController,
-                mDumpManager);
+                mDumpManager, mFeatureFlags) {
+            @Nullable
+            @Override
+            protected FabricatedOverlay getOverlay(int color, int type) {
+                FabricatedOverlay overlay = mock(FabricatedOverlay.class);
+                when(overlay.getIdentifier())
+                        .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
+                return overlay;
+            }
+        };
 
         mThemeOverlayController.start();
         if (USE_LOCK_SCREEN_WALLPAPER) {
@@ -106,10 +120,6 @@
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mDumpManager).registerDumpable(any(), any());
-
-        List<Integer> colorList = List.of(Color.RED, Color.BLUE, 0x0CCCCC, 0x000CCC);
-        when(mThemeOverlayApplier.getAvailableAccentColors()).thenReturn(colorList);
-        when(mThemeOverlayApplier.getAvailableSystemColors()).thenReturn(colorList);
     }
 
     @Test
@@ -128,17 +138,17 @@
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "FF0000");
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "0000FF");
+                .isEqualTo(new OverlayIdentifier("ff0000ff"));
 
         // Should not ask again if changed to same value
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
@@ -146,49 +156,6 @@
     }
 
     @Test
-    public void onWallpaperColorsChanged_whiteTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.WHITE),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_blackTheme() {
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.BLACK),
-                Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().containsKey(OVERLAY_CATEGORY_SYSTEM_PALETTE)).isFalse();
-    }
-
-    @Test
-    public void onWallpaperColorsChanged_addsLeadingZerosToColors() {
-        // Should ask for a new theme when wallpaper colors change
-        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(0x0CCCCC),
-                Color.valueOf(0x000CCC), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
-
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
-
-        // Assert that we received the colors that we were expecting
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo(MONET_SYSTEM_PALETTE_PACKAGE + "0CCCCC");
-        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(MONET_ACCENT_COLOR_PACKAGE + "000CCC");
-    }
-
-    @Test
     public void onWallpaperColorsChanged_preservesWallpaperPickerTheme() {
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -201,14 +168,37 @@
                 .thenReturn(jsonString);
 
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
-        ArgumentCaptor<Map<String, String>> themeOverlays = ArgumentCaptor.forClass(Map.class);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
 
-        verify(mThemeOverlayApplier).getAvailableSystemColors();
-        verify(mThemeOverlayApplier).getAvailableAccentColors();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(themeOverlays.capture(), any());
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
-                .isEqualTo("override.package.name");
+                .isEqualTo(new OverlayIdentifier("override.package.name"));
+    }
+
+    @Test
+    public void onWallpaperColorsChanged_parsesColorsFromWallpaperPicker() {
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.system_palette\":\"00FF00\"}";
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), any());
+
+        // Assert that we received the colors that we were expecting
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ff00ff00"));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index c743fd0..365c62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -57,8 +57,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -88,7 +87,6 @@
     private static final String TEXT = "Hello World";
     private static final int MESSAGE_RES_ID = R.id.message;
 
-    private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
     private Context mContextSpy;
     private ToastUI mToastUI;
     @Mock private LayoutInflater mLayoutInflater;
@@ -99,6 +97,7 @@
     @Mock private PluginManager mPluginManager;
     @Mock private DumpManager mDumpManager;
     @Mock private ToastLogger mToastLogger;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Mock private ITransientNotificationCallback mCallback;
     @Captor private ArgumentCaptor<View> mViewCaptor;
@@ -107,12 +106,9 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        // This is because inflate will result in WindowManager (WM) calls, which will fail since we
-        // are mocking it, so we mock LayoutInflater with the view obtained before mocking WM.
-        View view = ToastPresenter.getTextToastView(mContext, TEXT);
-        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(view);
-        mContext.addMockSystemService(LayoutInflater.class, mLayoutInflater);
+        when(mLayoutInflater.inflate(eq(TEXT_TOAST_LAYOUT), any())).thenReturn(
+                ToastPresenter.getTextToastView(mContext, TEXT));
+        when(mFeatureFlags.isToastStyleEnabled()).thenReturn(false);
 
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
         mContextSpy = spy(mContext);
@@ -120,8 +116,8 @@
 
         doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
         mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
-                mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
-                mFakeDelayableExecutor, mToastLogger);
+                mAccessibilityManager, new ToastFactory(mLayoutInflater, mPluginManager,
+                mDumpManager, mFeatureFlags), mToastLogger);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c0af15b..203ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -19,8 +19,8 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.NoCallingIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
@@ -66,7 +66,7 @@
     }
 
     @Override
-    public void setNoCallingIcons(String slot, List<NoCallingIconState> states) {
+    public void setCallIndicatorIcons(String slot, List<CallIndicatorIconState> states) {
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 76269dd..f1fc0b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -89,8 +89,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
@@ -297,7 +295,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index f4d96a12..ab329c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 5340ff7..9e10b21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -261,7 +261,7 @@
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController.getImpl(),
+                mBubbleController.asBubbles(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index b36626f..0d323fb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -23,6 +23,9 @@
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
 import java.io.PrintWriter;
 
 /**
@@ -31,11 +34,13 @@
 final class AccessibilityShellCommand extends ShellCommand {
     final @NonNull AccessibilityManagerService mService;
     final @NonNull SystemActionPerformer mSystemActionPerformer;
+    final @NonNull WindowManagerInternal mWindowManagerService;
 
     AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
             @NonNull SystemActionPerformer systemActionPerformer) {
         mService = service;
         mSystemActionPerformer = systemActionPerformer;
+        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
     }
 
     @Override
@@ -53,6 +58,10 @@
             case "call-system-action": {
                 return runCallSystemAction();
             }
+            case "start-trace":
+                return startTrace();
+            case "stop-trace":
+                return stopTrace();
         }
         return -1;
     }
@@ -98,6 +107,20 @@
         return -1;
     }
 
+    private int startTrace() {
+        WindowManagerInternal.AccessibilityControllerInternal ac =
+                mWindowManagerService.getAccessibilityController();
+        ac.startTrace();
+        return 0;
+    }
+
+    private int stopTrace() {
+        WindowManagerInternal.AccessibilityControllerInternal ac =
+                mWindowManagerService.getAccessibilityController();
+        ac.stopTrace();
+        return 0;
+    }
+
     private Integer parseUserId() {
         final String option = getNextOption();
         if (option != null) {
@@ -123,5 +146,9 @@
         pw.println("    Get whether binding to services provided by instant apps is allowed.");
         pw.println("  call-system-action <ACTION_ID>");
         pw.println("    Calls the system action with the given action id.");
+        pw.println("  start-trace");
+        pw.println("    Start the debug tracing.");
+        pw.println("  stop-trace");
+        pw.println("    Stop the debug tracing.");
     }
-}
\ No newline at end of file
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 25f4abe..903a071 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -19,12 +19,12 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
@@ -147,15 +147,15 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
         mMultiFingerGestures.add(
-                new MultiFingerMultiTapAndHold(
-                        mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this));
-        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTapAndHold(
                         mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, this));
         // Three-finger taps.
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP, this));
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 67f654e..6d72ca7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1492,7 +1492,7 @@
                 }
                 final Dataset dataset = (Dataset) result;
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
-                if (!isPinnedDataset(oldDataset)) {
+                if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 }
                 autoFill(requestId, datasetIdx, dataset, false);
@@ -1513,6 +1513,21 @@
     }
 
     /**
+     * Returns whether the dataset returned from the authentication result is ephemeral or not.
+     * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
+     * information.
+     */
+    private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset,
+            @NonNull Bundle authResultData) {
+        if (authResultData.containsKey(
+                AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) {
+            return authResultData.getBoolean(
+                    AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET);
+        }
+        return isPinnedDataset(oldDataset);
+    }
+
+    /**
      * A dataset can potentially have multiple fields, and it's possible that some of the fields'
      * has inline presentation and some don't. It's also possible that some of the fields'
      * inline presentation is pinned and some isn't. So the concept of whether a dataset is
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 96cfe02..9f36910 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -81,7 +81,6 @@
         ":dumpstate_aidl",
         ":framework_native_aidl",
         ":gsiservice_aidl",
-        ":idmap2_aidl",
         ":inputconstants_aidl",
         ":installd_aidl",
         ":storaged_aidl",
@@ -104,7 +103,6 @@
         "android.hardware.power-V1-java",
         "android.hardware.power-V1.0-java",
         "android.hardware.vibrator-V2-java",
-        "android.net.ipsec.ike.stubs.module_lib",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-permission.stubs.system_server",
@@ -131,8 +129,8 @@
         "android.hardware.light-V1-java",
         "android.hardware.tv.cec-V1.0-java",
         "android.hardware.weaver-V1.0-java",
-        "android.hardware.biometrics.face-V1.1-java",
         "android.hardware.biometrics.face-V1-java",
+        "android.hardware.biometrics.face-V1.0-java",
         "android.hardware.biometrics.fingerprint-V2.3-java",
         "android.hardware.biometrics.fingerprint-V1-java",
         "android.hardware.oemlock-V1.0-java",
@@ -230,8 +228,5 @@
         "java/com/android/server/connectivity/QosCallbackAgentConnection.java",
         "java/com/android/server/connectivity/QosCallbackTracker.java",
         "java/com/android/server/connectivity/TcpKeepaliveController.java",
-        "java/com/android/server/connectivity/Vpn.java",
-        "java/com/android/server/connectivity/VpnIkev2Utils.java",
-        "java/com/android/server/net/LockdownVpnTracker.java",
     ],
 }
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 737a9e4..342208c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -998,6 +998,18 @@
     public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
 
     /**
+     * Register to listen for loading progress of an installed package.
+     * The listener is automatically unregistered when the app is fully loaded.
+     * @param packageName The name of the installed package
+     * @param callback To loading reporting progress
+     * @param userId The user under which to check.
+     * @return Whether the registration was successful. It can fail if the package has not been
+     *          installed yet.
+     */
+    public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
+            @NonNull InstalledLoadingProgressCallback callback, int userId);
+
+    /**
      * Returns the string representation of a known package. For example,
      * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
      *
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 958c15c..e996eb4 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 
 import java.util.Collection;
 
@@ -37,6 +38,9 @@
      */
     public abstract String[] getMobileIfaces();
 
+    /** Returns CPU times for system server thread groups. */
+    public abstract SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes();
+
     /**
      * Inform battery stats how many deferred jobs existed when the app got launched and how
      * long ago was the last job execution for the app.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2f88351..558fbc2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
@@ -45,8 +44,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -95,6 +95,7 @@
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkStatsService;
+import android.net.IOnSetOemNetworkPreferenceListener;
 import android.net.IQosCallback;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
@@ -132,12 +133,13 @@
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
 import android.net.TetheringManager;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
-import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
@@ -168,8 +170,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.Credentials;
-import android.security.KeyStore;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -185,9 +185,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
@@ -212,9 +209,7 @@
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
-import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.utils.PriorityDump;
 
@@ -307,18 +302,7 @@
 
     private final PerUidCounter mNetworkRequestCounter;
 
-    private KeyStore mKeyStore;
-
-    @VisibleForTesting
-    @GuardedBy("mVpns")
-    protected final SparseArray<Vpn> mVpns = new SparseArray<>();
-
-    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
-    // a direct call to LockdownVpnTracker.isEnabled().
-    @GuardedBy("mVpns")
-    private boolean mLockdownEnabled;
-    @GuardedBy("mVpns")
-    private LockdownVpnTracker mLockdownTracker;
+    private volatile boolean mLockdownEnabled;
 
     /**
      * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
@@ -569,6 +553,12 @@
     private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
 
     /**
+     * used internally when setting the default networks for OemNetworkPreferences.
+     * obj = OemNetworkPreferences
+     */
+    private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -753,6 +743,27 @@
             }
         }
 
+        // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying
+        // network type, to preserve previous behaviour.
+        private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) {
+            if (vpnNai != mService.getLegacyLockdownNai()) return;
+
+            if (vpnNai.declaredUnderlyingNetworks == null
+                    || vpnNai.declaredUnderlyingNetworks.length != 1) {
+                Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: "
+                        + Arrays.toString(vpnNai.declaredUnderlyingNetworks));
+                return;
+            }
+            final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork(
+                    vpnNai.declaredUnderlyingNetworks[0]);
+            if (underlyingNai == null) return;
+
+            final int type = underlyingNai.networkInfo.getType();
+            final DetailedState state = DetailedState.CONNECTED;
+            maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */);
+            mService.sendLegacyNetworkBroadcast(underlyingNai, state, type);
+        }
+
         /** Adds the given network to the specified legacy type list. */
         public void add(int type, NetworkAgentInfo nai) {
             if (!isTypeSupported(type)) {
@@ -770,9 +781,17 @@
 
             // Send a broadcast if this is the first network of its type or if it's the default.
             final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
+
+            // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts
+            // to preserve previous behaviour.
+            final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED);
             if ((list.size() == 1) || isDefaultNetwork) {
-                maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
-                mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+                maybeLogBroadcast(nai, state, type, isDefaultNetwork);
+                mService.sendLegacyNetworkBroadcast(nai, state, type);
+            }
+
+            if (type == TYPE_VPN && state == DetailedState.CONNECTED) {
+                maybeSendLegacyLockdownBroadcast(nai);
             }
         }
 
@@ -967,13 +986,6 @@
         }
 
         /**
-         * Get a reference to the system keystore.
-         */
-        public KeyStore getKeyStore() {
-            return KeyStore.getInstance();
-        }
-
-        /**
          * @see ProxyTracker
          */
         public ProxyTracker makeProxyTracker(@NonNull Context context,
@@ -1037,10 +1049,10 @@
 
         mMetricsLog = logger;
         mNetworkRanker = new NetworkRanker();
-        final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
-                -1, NetworkRequest.Type.REQUEST);
-        mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder(),
-                null /* attributionTag */);
+        final NetworkRequest defaultInternetRequest = createDefaultRequest();
+        mDefaultRequest = new NetworkRequestInfo(
+                defaultInternetRequest, null, new Binder(),
+                null /* attributionTags */);
         mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
         mDefaultNetworkRequests.add(mDefaultRequest);
         mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
@@ -1082,7 +1094,6 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
-        mKeyStore = mDeps.getKeyStore();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = new LocationPermissionChecker(mContext);
@@ -1171,43 +1182,15 @@
 
         mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
 
-        // Set up the listener for user state for creating user VPNs.
+        // Listen for user add/removes to inform PermissionMonitor.
         // Should run on mHandler to avoid any races.
         IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_STARTED);
-        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
         intentFilter.addAction(Intent.ACTION_USER_ADDED);
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
 
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
-                mUserPresentReceiver,
-                new IntentFilter(Intent.ACTION_USER_PRESENT),
-                null /* broadcastPermission */,
-                null /* scheduler */);
-
-        // Listen to package add and removal events for all users.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        mUserAllContext.registerReceiver(
-                mIntentReceiver,
-                intentFilter,
-                null /* broadcastPermission */,
-                mHandler);
-
-        // Listen to lockdown VPN reset.
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
-        mUserAllContext.registerReceiver(
-                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+        mUserAllContext.registerReceiver(mIntentReceiver, intentFilter,
+                null /* broadcastPermission */, mHandler);
 
         mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS);
 
@@ -1248,21 +1231,29 @@
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
         netCap.setSingleUid(uid);
         return netCap;
     }
 
+    private NetworkRequest createDefaultRequest() {
+        return createDefaultInternetRequestForTransport(
+                TYPE_NONE, NetworkRequest.Type.REQUEST);
+    }
+
     private NetworkRequest createDefaultInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
-        if (transportType > -1) {
+        if (transportType > TYPE_NONE) {
             netCap.addTransportType(transportType);
         }
+        return createNetworkRequest(type, netCap);
+    }
+
+    private NetworkRequest createNetworkRequest(
+            NetworkRequest.Type type, NetworkCapabilities netCap) {
         return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type);
     }
 
@@ -1312,7 +1303,8 @@
 
         if (enable) {
             handleRegisterNetworkRequest(new NetworkRequestInfo(
-                    null, networkRequest, new Binder(), null /* attributionTag */));
+                    networkRequest, null, new Binder(),
+                    null /* attributionTags */));
         } else {
             handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
                     /* callOnUnavailable */ false);
@@ -1385,9 +1377,7 @@
     }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
-        synchronized (mVpns) {
-            if (mLockdownEnabled) return null;
-        }
+        if (mLockdownEnabled) return null;
         final NetworkAgentInfo nai = getVpnForUid(uid);
         if (nai != null) return nai.declaredUnderlyingNetworks;
         return null;
@@ -1472,11 +1462,9 @@
         if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
             networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                mLockdownTracker.augmentNetworkInfo(networkInfo);
-            }
-        }
+        networkInfo.setDetailedState(
+                getLegacyLockdownState(networkInfo.getDetailedState()),
+                "" /* reason */, null /* extraInfo */);
     }
 
     /**
@@ -1535,14 +1523,6 @@
         return nai.network;
     }
 
-    // Public because it's used by mLockdownTracker.
-    public NetworkInfo getActiveNetworkInfoUnfiltered() {
-        enforceAccessPermission();
-        final int uid = mDeps.getCallingUid();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return state.networkInfo;
-    }
-
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
         NetworkStack.checkNetworkStackPermission(mContext);
@@ -2164,22 +2144,6 @@
                 isBackgroundRestricted);
     }
 
-    /**
-     * Require that the caller is either in the same user or has appropriate permission to interact
-     * across users.
-     *
-     * @param userId Target user for whatever operation the current IPC is supposed to perform.
-     */
-    private void enforceCrossUserPermission(int userId) {
-        if (userId == UserHandle.getCallingUserId()) {
-            // Not a cross-user call.
-            return;
-        }
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                "ConnectivityService");
-    }
-
     private boolean checkAnyPermissionOf(String... permissions) {
         for (String permission : permissions) {
             if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
@@ -2260,12 +2224,6 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
     }
 
-    private void enforceControlAlwaysOnVpnPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
-                "ConnectivityService");
-    }
-
     private void enforceNetworkStackOrSettingsPermission() {
         enforceAnyPermissionOf(
                 android.Manifest.permission.NETWORK_SETTINGS,
@@ -2290,6 +2248,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private void enforceOemNetworkPreferencesPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE,
+                "ConnectivityService");
+    }
+
     private boolean checkNetworkStackPermission() {
         return checkAnyPermissionOf(
                 android.Manifest.permission.NETWORK_STACK,
@@ -2338,13 +2302,6 @@
     }
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                info = new NetworkInfo(info);
-                mLockdownTracker.augmentNetworkInfo(info);
-            }
-        }
-
         Intent intent = new Intent(bcastType);
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
         intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -2438,10 +2395,6 @@
             }
         }
 
-        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
-        // for user to unlock device too.
-        updateLockdownVpn();
-
         // Create network requests for always-on networks.
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
     }
@@ -2632,6 +2585,12 @@
         }
         pw.println();
 
+        pw.print("Current per-app default networks: ");
+        pw.increaseIndent();
+        dumpPerAppNetworkPreferences(pw);
+        pw.decreaseIndent();
+        pw.println();
+
         pw.println("Current Networks:");
         pw.increaseIndent();
         dumpNetworks(pw);
@@ -2752,6 +2711,40 @@
         }
     }
 
+    private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) {
+        pw.println("Per-App Network Preference:");
+        pw.increaseIndent();
+        if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) {
+            pw.println("none");
+        } else {
+            pw.println(mOemNetworkPreferences.toString());
+        }
+        pw.decreaseIndent();
+
+        for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) {
+            if (mDefaultRequest == defaultRequest) {
+                continue;
+            }
+
+            final boolean isActive = null != defaultRequest.getSatisfier();
+            pw.println("Is per-app network active:");
+            pw.increaseIndent();
+            pw.println(isActive);
+            if (isActive) {
+                pw.println("Active network: " + defaultRequest.getSatisfier().network.netId);
+            }
+            pw.println("Tracked UIDs:");
+            pw.increaseIndent();
+            if (0 == defaultRequest.mRequests.size()) {
+                pw.println("none, this should never occur.");
+            } else {
+                pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids());
+            }
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
     private void dumpNetworkRequests(IndentingPrintWriter pw) {
         for (NetworkRequestInfo nri : requestsSortedById()) {
             pw.println(nri.toString());
@@ -2885,7 +2878,15 @@
                         Log.wtf(TAG, "Non-virtual networks cannot have underlying networks");
                         break;
                     }
+
                     final List<Network> underlying = (List<Network>) arg.second;
+
+                    if (isLegacyLockdownNai(nai)
+                            && (underlying == null || underlying.size() != 1)) {
+                        Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString()
+                                + " must have exactly one underlying network: " + underlying);
+                    }
+
                     final Network[] oldUnderlying = nai.declaredUnderlyingNetworks;
                     nai.declaredUnderlyingNetworks = (underlying != null)
                             ? underlying.toArray(new Network[0]) : null;
@@ -3494,7 +3495,6 @@
                     //  incorrect) behavior.
                     mNetworkActivityTracker.updateDataActivityTracking(
                             null /* newNetwork */, nai);
-                    notifyLockdownVpn(nai);
                     ensureNetworkTransitionWakelock(nai.toShortString());
                 }
             }
@@ -3584,29 +3584,38 @@
     }
 
     private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+        handleRegisterNetworkRequest(Collections.singletonList(nri));
+    }
+
+    private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
-        mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        for (final NetworkRequest req : nri.mRequests) {
-            mNetworkRequests.put(req, nri);
-            if (req.isListen()) {
-                for (final NetworkAgentInfo network : mNetworkAgentInfos) {
-                    if (req.networkCapabilities.hasSignalStrength()
-                            && network.satisfiesImmutableCapabilitiesOf(req)) {
-                        updateSignalStrengthThresholds(network, "REGISTER", req);
+        for (final NetworkRequestInfo nri : nris) {
+            mNetworkRequestInfoLogs.log("REGISTER " + nri);
+            for (final NetworkRequest req : nri.mRequests) {
+                mNetworkRequests.put(req, nri);
+                if (req.isListen()) {
+                    for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+                        if (req.networkCapabilities.hasSignalStrength()
+                                && network.satisfiesImmutableCapabilitiesOf(req)) {
+                            updateSignalStrengthThresholds(network, "REGISTER", req);
+                        }
                     }
                 }
             }
         }
-        rematchAllNetworksAndRequests();
-        // If the nri is satisfied, return as its score has already been sent if needed.
-        if (nri.isBeingSatisfied()) {
-            return;
-        }
 
-        // As this request was not satisfied on rematch and thus never had any scores sent to the
-        // factories, send null now for each request of type REQUEST.
-        for (final NetworkRequest req : nri.mRequests) {
-            if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+        rematchAllNetworksAndRequests();
+        for (final NetworkRequestInfo nri : nris) {
+            // If the nri is satisfied, return as its score has already been sent if needed.
+            if (nri.isBeingSatisfied()) {
+                return;
+            }
+
+            // As this request was not satisfied on rematch and thus never had any scores sent to
+            // the factories, send null now for each request of type REQUEST.
+            for (final NetworkRequest req : nri.mRequests) {
+                if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+            }
         }
     }
 
@@ -3779,6 +3788,7 @@
                 removeListenRequestFromNetworks(req);
             }
         }
+        mDefaultNetworkRequests.remove(nri);
         mNetworkRequestCounter.decrementCount(nri.mUid);
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
 
@@ -4417,6 +4427,16 @@
                 case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
                     handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
                     break;
+                case EVENT_SET_OEM_NETWORK_PREFERENCE:
+                    final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg =
+                            (Pair<OemNetworkPreferences,
+                                    IOnSetOemNetworkPreferenceListener>) msg.obj;
+                    try {
+                        handleSetOemNetworkPreference(arg.first, arg.second);
+                    } catch (RemoteException e) {
+                        loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e);
+                    }
+                    break;
             }
         }
     }
@@ -4719,183 +4739,6 @@
     }
 
     /**
-     * Prepare for a VPN application.
-     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
-     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param oldPackage Package name of the application which currently controls VPN, which will
-     *                   be replaced. If there is no such application, this should should either be
-     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
-     * @param newPackage Package name of the application which should gain control of VPN, or
-     *                   {@code null} to disable.
-     * @param userId User for whom to prepare the new VPN.
-     *
-     * @hide
-     */
-    @Override
-    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
-            int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
-     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
-     * class. If the caller is not {@code userId}, {@link
-     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
-     *
-     * @param packageName The package for which authorization state should change.
-     * @param userId User for whom {@code packageName} is installed.
-     * @param authorized {@code true} if this app should be able to start a VPN connection without
-     *     explicit user approval, {@code false} if not.
-     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
-     *     permissions should be granted. When unauthorizing an app, {@link
-     *     VpnManager.TYPE_VPN_NONE} should be used.
-     * @hide
-     */
-    @Override
-    public void setVpnPackageAuthorization(
-            String packageName, int userId, @VpnManager.VpnType int vpnType) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                vpn.setPackageAuthorization(packageName, vpnType);
-            }
-        }
-    }
-
-    /**
-     * Configure a TUN interface and return its file descriptor. Parameters
-     * are encoded and opaque to this class. This method is used by VpnBuilder
-     * and not available in ConnectivityManager. Permissions are checked in
-     * Vpn class.
-     * @hide
-     */
-    @Override
-    public ParcelFileDescriptor establishVpn(VpnConfig config) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).establish(config);
-        }
-    }
-
-    /**
-     * Stores the given VPN profile based on the provisioning package name.
-     *
-     * <p>If there is already a VPN profile stored for the provisioning package, this call will
-     * overwrite the profile.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @return {@code true} if user consent has already been granted, {@code false} otherwise.
-     * @hide
-     */
-    @Override
-    public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
-        }
-    }
-
-    /**
-     * Deletes the stored VPN profile for the provisioning package
-     *
-     * <p>If there are no profiles for the given package, this method will silently succeed.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void deleteVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Starts the VPN based on the stored profile for the given package
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @throws IllegalArgumentException if no profile was found for the given package name.
-     * @hide
-     */
-    @Override
-    public void startVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
-        }
-    }
-
-    /**
-     * Stops the Platform VPN if the provided package is running one.
-     *
-     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
-     * exclusively by the Settings app, and passed into the platform at startup time.
-     *
-     * @hide
-     */
-    @Override
-    public void stopVpnProfile(@NonNull String packageName) {
-        final int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            mVpns.get(user).stopVpnProfile(packageName);
-        }
-    }
-
-    /**
-     * Start legacy VPN, controlling native daemons as needed. Creates a
-     * secondary thread to perform connection work, returning quickly.
-     */
-    @Override
-    public void startLegacyVpn(VpnProfile profile) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final LinkProperties egress = getActiveLinkProperties();
-        if (egress == null) {
-            throw new IllegalStateException("Missing active network connection");
-        }
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
-        }
-    }
-
-    /**
-     * Return the information of the ongoing legacy VPN. This method is used
-     * by VpnSettings and not available in ConnectivityManager. Permissions
-     * are checked in Vpn class.
-     */
-    @Override
-    public LegacyVpnInfo getLegacyVpnInfo(int userId) {
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            return mVpns.get(userId).getLegacyVpnInfo();
-        }
-    }
-
-    /**
      * Return the information of all ongoing VPNs.
      *
      * <p>This method is used to update NetworkStatsService.
@@ -4904,10 +4747,8 @@
      */
     private UnderlyingNetworkInfo[] getAllVpnInfo() {
         ensureRunningOnConnectivityServiceThread();
-        synchronized (mVpns) {
-            if (mLockdownEnabled) {
-                return new UnderlyingNetworkInfo[0];
-            }
+        if (mLockdownEnabled) {
+            return new UnderlyingNetworkInfo[0];
         }
         List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
@@ -4963,25 +4804,6 @@
                 nai.linkProperties.getInterfaceName(), interfaces);
     }
 
-    /**
-     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
-     * VpnDialogs and not available in ConnectivityManager.
-     * Permissions are checked in Vpn class.
-     * @hide
-     */
-    @Override
-    public VpnConfig getVpnConfig(int userId) {
-        enforceCrossUserPermission(userId);
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn != null) {
-                return vpn.getVpnConfig();
-            } else {
-                return null;
-            }
-        }
-    }
-
     // TODO This needs to be the default network that applies to the NAI.
     private Network[] underlyingNetworksOrDefault(final int ownerUid,
             Network[] underlyingNetworks) {
@@ -5069,195 +4891,54 @@
         mVpnBlockedUidRanges = newVpnBlockedUidRanges;
     }
 
-    private boolean isLockdownVpnEnabled() {
-        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
-    }
-
     @Override
-    public boolean updateLockdownVpn() {
-        // Allow the system UID for the system server and for Settings.
-        // Also, for unit tests, allow the process that ConnectivityService is running in.
-        if (mDeps.getCallingUid() != Process.SYSTEM_UID
-                && Binder.getCallingPid() != Process.myPid()) {
-            logw("Lockdown VPN only available to system process or AID_SYSTEM");
-            return false;
-        }
-
-        synchronized (mVpns) {
-            // Tear down existing lockdown if profile was removed
-            mLockdownEnabled = isLockdownVpnEnabled();
-            if (mLockdownEnabled) {
-                byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
-                if (profileTag == null) {
-                    loge("Lockdown VPN configured but cannot be read from keystore");
-                    return false;
-                }
-                String profileName = new String(profileTag);
-                final VpnProfile profile = VpnProfile.decode(
-                        profileName, mKeyStore.get(Credentials.VPN + profileName));
-                if (profile == null) {
-                    loge("Lockdown VPN configured invalid profile " + profileName);
-                    setLockdownTracker(null);
-                    return true;
-                }
-                int user = UserHandle.getUserId(mDeps.getCallingUid());
-                Vpn vpn = mVpns.get(user);
-                if (vpn == null) {
-                    logw("VPN for user " + user + " not ready yet. Skipping lockdown");
-                    return false;
-                }
-                setLockdownTracker(
-                        new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn,  profile));
-            } else {
-                setLockdownTracker(null);
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
-     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
-     */
-    @GuardedBy("mVpns")
-    private void setLockdownTracker(LockdownVpnTracker tracker) {
-        // Shutdown any existing tracker
-        final LockdownVpnTracker existing = mLockdownTracker;
-        // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
-        // necessary onBlockedStatusChanged callbacks.
-        mLockdownTracker = null;
-        if (existing != null) {
-            existing.shutdown();
-        }
-
-        if (tracker != null) {
-            mLockdownTracker = tracker;
-            mLockdownTracker.init();
-        }
-    }
-
-    /**
-     * Throws if there is any currently running, always-on Legacy VPN.
-     *
-     * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
-     * running across the entire system. Tracking for app-based VPNs is done on a per-user,
-     * per-package basis in Vpn.java
-     */
-    @GuardedBy("mVpns")
-    private void throwIfLockdownEnabled() {
-        if (mLockdownEnabled) {
-            throw new IllegalStateException("Unavailable in lockdown mode");
-        }
-    }
-
-    /**
-     * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
-     * some setup and then call {@code establish()} to connect.
-     *
-     * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
-     */
-    private boolean startAlwaysOnVpn(int userId) {
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                // Shouldn't happen as all code paths that point here should have checked the Vpn
-                // exists already.
-                Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
-                return false;
-            }
-
-            return vpn.startAlwaysOnVpn(mKeyStore);
-        }
-    }
-
-    @Override
-    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+    public void setLegacyLockdownVpnEnabled(boolean enabled) {
         enforceSettingsPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
-        }
+        mHandler.post(() -> mLockdownEnabled = enabled);
     }
 
-    @Override
-    public boolean setAlwaysOnVpnPackage(
-            int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
-            if (isLockdownVpnEnabled()) {
-                return false;
-            }
-
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
-                return false;
-            }
-            if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-                return false;
-            }
-        }
-        return true;
+    private boolean isLegacyLockdownNai(NetworkAgentInfo nai) {
+        return mLockdownEnabled
+                && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY
+                && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID);
     }
 
-    @Override
-    public String getAlwaysOnVpnPackage(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getAlwaysOnPackage();
+    private NetworkAgentInfo getLegacyLockdownNai() {
+        if (!mLockdownEnabled) {
+            return null;
         }
-    }
+        // The legacy lockdown VPN always only applies to userId 0.
+        final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID);
+        if (nai == null || !isLegacyLockdownNai(nai)) return null;
 
-    @Override
-    public boolean isVpnLockdownEnabled(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return false;
-            }
-            return vpn.getLockdown();
+        // The legacy lockdown VPN must always have exactly one underlying network.
+        // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in
+        // a local variable. There is no need to make a copy because its contents cannot change.
+        final Network[] underlying = nai.declaredUnderlyingNetworks;
+        if (underlying == null ||  underlying.length != 1) {
+            return null;
         }
-    }
 
-    @Override
-    public List<String> getVpnLockdownWhitelist(int userId) {
-        enforceControlAlwaysOnVpnPermission();
-        enforceCrossUserPermission(userId);
-
-        synchronized (mVpns) {
-            Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                logw("User " + userId + " has no Vpn configuration");
-                return null;
-            }
-            return vpn.getLockdownAllowlist();
+        // The legacy lockdown VPN always uses the default network.
+        // If the VPN's underlying network is no longer the current default network, it means that
+        // the default network has just switched, and the VPN is about to disconnect.
+        // Report that the VPN is not connected, so when the state of NetworkInfo objects
+        // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED.
+        final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+        if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
+            return null;
         }
+
+        return nai;
+    };
+
+    private DetailedState getLegacyLockdownState(DetailedState origState) {
+        if (origState != DetailedState.CONNECTED) {
+            return origState;
+        }
+        return (mLockdownEnabled && getLegacyLockdownNai() == null)
+                ? DetailedState.CONNECTING
+                : DetailedState.CONNECTED;
     }
 
     @Override
@@ -5292,111 +4973,12 @@
         }
     }
 
-    private void onUserStarted(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn != null) {
-                loge("Starting user already has a VPN");
-                return;
-            }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
-            mVpns.put(userId, userVpn);
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            }
-        }
-    }
-
-    private void onUserStopped(int userId) {
-        synchronized (mVpns) {
-            Vpn userVpn = mVpns.get(userId);
-            if (userVpn == null) {
-                loge("Stopped user has no VPN");
-                return;
-            }
-            userVpn.onUserStopped();
-            mVpns.delete(userId);
-        }
-    }
-
     private void onUserAdded(int userId) {
         mPermissionMonitor.onUserAdded(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserAdded(userId);
-            }
-        }
     }
 
     private void onUserRemoved(int userId) {
         mPermissionMonitor.onUserRemoved(userId);
-        synchronized (mVpns) {
-            final int vpnsSize = mVpns.size();
-            for (int i = 0; i < vpnsSize; i++) {
-                Vpn vpn = mVpns.valueAt(i);
-                vpn.onUserRemoved(userId);
-            }
-        }
-    }
-
-    private void onPackageReplaced(String packageName, int uid) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
-            return;
-        }
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
-                log("Restarting always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.startAlwaysOnVpn(mKeyStore);
-            }
-        }
-    }
-
-    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
-        if (TextUtils.isEmpty(packageName) || uid < 0) {
-            Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
-            return;
-        }
-
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(userId);
-            if (vpn == null) {
-                return;
-            }
-            // Legacy always-on VPN won't be affected since the package name is not set.
-            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
-                log("Removing always-on VPN package " + packageName + " for user "
-                        + userId);
-                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
-            }
-        }
-    }
-
-    private void onUserUnlocked(int userId) {
-        synchronized (mVpns) {
-            // User present may be sent because of an unlock, which might mean an unlocked keystore.
-            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
-                updateLockdownVpn();
-            } else {
-                startAlwaysOnVpn(userId);
-            }
-        }
-    }
-
-    private void onVpnLockdownReset() {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) mLockdownTracker.reset();
-        }
     }
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -5405,52 +4987,20 @@
             ensureRunningOnConnectivityServiceThread();
             final String action = intent.getAction();
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            final Uri packageData = intent.getData();
-            final String packageName =
-                    packageData != null ? packageData.getSchemeSpecificPart() : null;
-
-            if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
-                onVpnLockdownReset();
-            }
 
             // UserId should be filled for below intents, check the existence.
             if (userId == UserHandle.USER_NULL) return;
 
-            if (Intent.ACTION_USER_STARTED.equals(action)) {
-                onUserStarted(userId);
-            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
-                onUserStopped(userId);
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
                 onUserAdded(userId);
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 onUserRemoved(userId);
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                onUserUnlocked(userId);
-            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                onPackageReplaced(packageName, uid);
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                final boolean isReplacing = intent.getBooleanExtra(
-                        Intent.EXTRA_REPLACING, false);
-                onPackageRemoved(packageName, uid, isReplacing);
-            } else {
+            }  else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
         }
     };
 
-    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // Try creating lockdown tracker, since user present usually means
-            // unlocked keystore.
-            updateLockdownVpn();
-            // Use the same context that registered receiver before to unregister it. Because use
-            // different context to unregister receiver will cause exception.
-            context.unregisterReceiver(this);
-        }
-    };
-
     private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
@@ -5549,10 +5099,12 @@
 
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
+        @Nullable
+        final Messenger mMessenger;
+        @Nullable
         private final IBinder mBinder;
         final int mPid;
         final int mUid;
-        final Messenger messenger;
         @Nullable
         final String mCallingAttributionTag;
 
@@ -5568,12 +5120,17 @@
             return uids;
         }
 
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi,
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi,
                 @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), pi, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r,
+                @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) {
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mPendingIntent = pi;
-            messenger = null;
+            mMessenger = null;
             mBinder = null;
             mPid = getCallingPid();
             mUid = mDeps.getCallingUid();
@@ -5581,11 +5138,16 @@
             mCallingAttributionTag = callingAttributionTag;
         }
 
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder,
-                @Nullable String callingAttributionTag) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
+            this(Collections.singletonList(r), m, binder, callingAttributionTag);
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m,
+                @Nullable final IBinder binder, @Nullable String callingAttributionTag) {
             super();
-            messenger = m;
             mRequests = initializeRequests(r);
+            mMessenger = m;
             ensureAllNetworkRequestsHaveType(mRequests);
             mBinder = binder;
             mPid = getCallingPid();
@@ -5601,7 +5163,11 @@
             }
         }
 
-        NetworkRequestInfo(NetworkRequest r) {
+        NetworkRequestInfo(@NonNull final NetworkRequest r) {
+            this(Collections.singletonList(r));
+        }
+
+        NetworkRequestInfo(@NonNull final List<NetworkRequest> r) {
             this(r, null /* pi */, null /* callingAttributionTag */);
         }
 
@@ -5616,9 +5182,10 @@
             return mRequests.size() > 1;
         }
 
-        private List<NetworkRequest> initializeRequests(NetworkRequest r) {
-            final ArrayList<NetworkRequest> tempRequests = new ArrayList<>();
-            tempRequests.add(new NetworkRequest(r));
+        private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) {
+            // Creating a defensive copy to prevent the sender from modifying the list being
+            // reflected in the return value of this method.
+            final List<NetworkRequest> tempRequests = new ArrayList<>(r);
             return Collections.unmodifiableList(tempRequests);
         }
 
@@ -5747,6 +5314,7 @@
                 throw new SecurityException("Insufficient permissions to specify legacy type");
             }
         }
+        final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities;
         final int callingUid = mDeps.getCallingUid();
         final NetworkRequest.Type reqType;
         try {
@@ -5757,11 +5325,15 @@
         switch (reqType) {
             case TRACK_DEFAULT:
                 // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities}
-                // is unused and will be replaced by the one from the default network request.
-                // This allows callers to keep track of the system default network.
+                // is unused and will be replaced by ones appropriate for the caller.
+                // This allows callers to keep track of the default network for their app.
                 networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
                 enforceAccessPermission();
                 break;
+            case TRACK_SYSTEM_DEFAULT:
+                enforceSettingsPermission();
+                networkCapabilities = new NetworkCapabilities(defaultNc);
+                break;
             case BACKGROUND_REQUEST:
                 enforceNetworkStackOrSettingsPermission();
                 // Fall-through since other checks are the same with normal requests.
@@ -5780,6 +5352,7 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
+
         // Set the UID range for this request to the single UID of the requester, or to an empty
         // set of UIDs if the caller has the appropriate permission and UIDs have not been set.
         // This will overwrite any allowed UIDs in the requested capabilities. Though there
@@ -5796,9 +5369,20 @@
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
         NetworkRequestInfo nri =
-                new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
         if (DBG) log("requestNetwork for " + nri);
 
+        // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
+        // copied from the default request above. (This is necessary to ensure, for example, that
+        // the callback does not leak sensitive information to unprivileged apps.) Check that the
+        // changes don't alter request matching.
+        if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT &&
+                (!networkCapabilities.equalRequestableCapabilities(defaultNc))) {
+            throw new IllegalStateException(
+                    "TRACK_SYSTEM_DEFAULT capabilities don't match default request: "
+                    + networkCapabilities + " vs. " + defaultNc);
+        }
+
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
         if (timeoutMs > 0) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
@@ -5952,7 +5536,7 @@
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
         NetworkRequestInfo nri =
-                new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag);
+                new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag);
         if (VDBG) log("listenForNetwork for " + nri);
 
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
@@ -6080,13 +5664,20 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
+    // Current OEM network preferences.
+    @NonNull
+    private OemNetworkPreferences mOemNetworkPreferences =
+            new OemNetworkPreferences.Builder().build();
+
     // The always-on request for an Internet-capable network that apps without a specific default
     // fall back to.
+    @VisibleForTesting
     @NonNull
-    private final NetworkRequestInfo mDefaultRequest;
+    final NetworkRequestInfo mDefaultRequest;
     // Collection of NetworkRequestInfo's used for default networks.
+    @VisibleForTesting
     @NonNull
-    private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
+    final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
 
     private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
         return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
@@ -6389,20 +5980,18 @@
                     Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
         }
 
-        // Prioritize the user portal URL from the network agent.
-        if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null
-                || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) {
-            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl());
+        // Prioritize the user portal URL from the network agent if the source is authenticated.
+        if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(),
+                    apiData.getUserPortalUrlSource());
         }
-        // Prioritize the venue information URL from the network agent.
-        if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null
-                || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) {
-            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl());
-
-            // Note that venue friendly name can only come from the network agent because it is not
-            // in use in RFC8908. However, if using the Capport venue URL, make sure that the
-            // friendly name is not set from the network agent.
-            captivePortalBuilder.setVenueFriendlyName(null);
+        // Prioritize the venue information URL from the network agent if the source is
+        // authenticated.
+        if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource()
+                != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) {
+            captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(),
+                    apiData.getVenueInfoUrlSource());
         }
         return captivePortalBuilder.build();
     }
@@ -7165,7 +6754,7 @@
     private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
             @NonNull final NetworkAgentInfo networkAgent, final int notificationType,
             final int arg1) {
-        if (nri.messenger == null) {
+        if (nri.mMessenger == null) {
             // Default request has no msgr. Also prevents callbacks from being invoked for
             // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
             // are Type.LISTEN, but should not have NetworkCallbacks invoked.
@@ -7234,7 +6823,7 @@
                 String notification = ConnectivityManager.getCallbackName(notificationType);
                 log("sending notification " + notification + " for " + nrForCallback);
             }
-            nri.messenger.send(msg);
+            nri.mMessenger.send(msg);
         } catch (RemoteException e) {
             // may occur naturally in the race of binder death.
             loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
@@ -7323,7 +6912,6 @@
             mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
         }
         mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
-        notifyLockdownVpn(newDefaultNetwork);
         handleApplyDefaultProxy(null != newDefaultNetwork
                 ? newDefaultNetwork.linkProperties.getHttpProxy() : null);
         updateTcpBufferSizes(null != newDefaultNetwork
@@ -7781,12 +7369,6 @@
                 mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
                 mLegacyTypeTracker.add(
                         newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
-                // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
-                // to reflect the NetworkInfo of this new network. This broadcast has to be sent
-                // after the disconnect broadcasts above, but before the broadcasts sent by the
-                // legacy type tracker below.
-                // TODO : refactor this, it's too complex
-                notifyLockdownVpn(newDefaultNetwork);
             }
         }
 
@@ -7844,18 +7426,6 @@
         sendInetConditionBroadcast(nai.networkInfo);
     }
 
-    private void notifyLockdownVpn(NetworkAgentInfo nai) {
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                if (nai != null && nai.isVPN()) {
-                    mLockdownTracker.onVpnStateChanged(nai.networkInfo);
-                } else {
-                    mLockdownTracker.onNetworkInfoChanged();
-                }
-            }
-        }
-    }
-
     @NonNull
     private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) {
         final NetworkInfo newInfo = new NetworkInfo(info);
@@ -7894,7 +7464,6 @@
             oldInfo = networkAgent.networkInfo;
             networkAgent.networkInfo = newInfo;
         }
-        notifyLockdownVpn(networkAgent);
 
         if (DBG) {
             log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from "
@@ -8195,34 +7764,6 @@
     }
 
     @Override
-    public boolean addVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).addAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean removeVpnAddress(String address, int prefixLength) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        synchronized (mVpns) {
-            throwIfLockdownEnabled();
-            return mVpns.get(user).removeAddress(address, prefixLength);
-        }
-    }
-
-    @Override
-    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
-        int user = UserHandle.getUserId(mDeps.getCallingUid());
-        final boolean success;
-        synchronized (mVpns) {
-            success = mVpns.get(user).setUnderlyingNetworks(networks);
-        }
-        return success;
-    }
-
-    @Override
     public String getCaptivePortalServerUrl() {
         enforceNetworkStackOrSettingsPermission();
         String settingUrl = mContext.getResources().getString(
@@ -8301,8 +7842,6 @@
             return;
         }
 
-        final int userId = UserHandle.getCallingUserId();
-
         final long token = Binder.clearCallingIdentity();
         try {
             final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
@@ -8314,44 +7853,6 @@
         // Turn airplane mode off
         setAirplaneMode(false);
 
-        if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
-            // Remove always-on package
-            synchronized (mVpns) {
-                final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
-                if (alwaysOnPackage != null) {
-                    setAlwaysOnVpnPackage(userId, null, false, null);
-                    setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
-                }
-
-                // Turn Always-on VPN off
-                if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        mKeyStore.delete(Credentials.LOCKDOWN_VPN);
-                        mLockdownEnabled = false;
-                        setLockdownTracker(null);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-
-                // Turn VPN off
-                VpnConfig vpnConfig = getVpnConfig(userId);
-                if (vpnConfig != null) {
-                    if (vpnConfig.legacy) {
-                        prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
-                    } else {
-                        // Prevent this app (packagename = vpnConfig.user) from initiating
-                        // VPN connections in the future without user intervention.
-                        setVpnPackageAuthorization(
-                                vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
-
-                        prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
-                    }
-                }
-            }
-        }
-
         // restore private DNS settings to default mode (opportunistic)
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) {
             Settings.Global.putString(mContext.getContentResolver(),
@@ -8443,41 +7944,11 @@
         }
     }
 
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner() {
-        return getVpnIfOwner(mDeps.getCallingUid());
-    }
-
-    // TODO: stop calling into Vpn.java and get this information from data in this class.
-    @GuardedBy("mVpns")
-    private Vpn getVpnIfOwner(int uid) {
-        final int user = UserHandle.getUserId(uid);
-
-        final Vpn vpn = mVpns.get(user);
-        if (vpn == null) {
-            return null;
-        } else {
-            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
-            return (info == null || info.ownerUid != uid) ? null : vpn;
-        }
-    }
-
-    /**
-     * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
-     * for testing.
-     */
-    private Vpn enforceActiveVpnOrNetworkStackPermission() {
-        if (checkNetworkStackPermission()) {
-            return null;
-        }
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            if (vpn != null) {
-                return vpn;
-            }
-        }
-        throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
-                + "permission");
+    private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+        if (vpn == null) return VpnManager.TYPE_VPN_NONE;
+        final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
+        if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
+        return ((VpnTransportInfo) ti).type;
     }
 
     /**
@@ -8487,14 +7958,6 @@
      * connection is not found.
      */
     public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
-        final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
-
-        // Only VpnService based VPNs should be able to get this information.
-        if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
-            throw new SecurityException(
-                    "getConnectionOwnerUid() not allowed for non-VpnService VPNs");
-        }
-
         if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
             throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
         }
@@ -8502,30 +7965,21 @@
         final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
                 connectionInfo.local, connectionInfo.remote);
 
-        /* Filter out Uids not associated with the VPN. */
-        if (vpn != null && !vpn.appliesToUid(uid)) {
+        if (uid == INVALID_UID) return uid;  // Not found.
+
+        // Connection owner UIDs are visible only to the network stack and to the VpnService-based
+        // VPN, if any, that applies to the UID that owns the connection.
+        if (checkNetworkStackPermission()) return uid;
+
+        final NetworkAgentInfo vpn = getVpnForUid(uid);
+        if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE
+                || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) {
             return INVALID_UID;
         }
 
         return uid;
     }
 
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getAlwaysOn();
-        }
-    }
-
-    @Override
-    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
-        synchronized (mVpns) {
-            Vpn vpn = getVpnIfOwner();
-            return vpn != null && vpn.getLockdown();
-        }
-    }
-
     /**
      * Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
      *
@@ -9201,9 +8655,212 @@
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
+    private void enforceAutomotiveDevice() {
+        final boolean isAutomotiveDevice =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+        if (!isAutomotiveDevice) {
+            throw new UnsupportedOperationException(
+                    "setOemNetworkPreference() is only available on automotive devices.");
+        }
+    }
+
+    /**
+     * Used by automotive devices to set the network preferences used to direct traffic at an
+     * application level as per the given OemNetworkPreferences. An example use-case would be an
+     * automotive OEM wanting to provide connectivity for applications critical to the usage of a
+     * vehicle via a particular network.
+     *
+     * Calling this will overwrite the existing preference.
+     *
+     * @param preference {@link OemNetworkPreferences} The application network preference to be set.
+     * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used
+     * to communicate completion of setOemNetworkPreference();
+     */
     @Override
-    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
-        // TODO http://b/176495594 track multiple default networks with networkPreferences
-        if (DBG) log("setOemNetworkPreference() called with: " + preference.toString());
+    public void setOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @Nullable final IOnSetOemNetworkPreferenceListener listener) {
+
+        enforceAutomotiveDevice();
+        enforceOemNetworkPreferencesPermission();
+
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        validateOemNetworkPreferences(preference);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE,
+                new Pair<>(preference, listener)));
+    }
+
+    private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) {
+        for (@OemNetworkPreferences.OemNetworkPreference final int pref
+                : preference.getNetworkPreferences().values()) {
+            if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) {
+                final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value.";
+                throw new IllegalArgumentException(msg);
+            }
+        }
+    }
+
+    private void handleSetOemNetworkPreference(
+            @NonNull final OemNetworkPreferences preference,
+            @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException {
+        Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null");
+        if (DBG) {
+            log("set OEM network preferences :" + preference.toString());
+        }
+        final List<NetworkRequestInfo> nris =
+                new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference);
+        updateDefaultNetworksForOemNetworkPreference(nris);
+        mOemNetworkPreferences = preference;
+        // TODO http://b/176496396 persist data to shared preferences.
+
+        if (null != listener) {
+            listener.onComplete();
+        }
+    }
+
+    private void updateDefaultNetworksForOemNetworkPreference(
+            @NonNull final List<NetworkRequestInfo> nris) {
+        ensureRunningOnConnectivityServiceThread();
+        clearNonDefaultNetworkAgents();
+        addDefaultNetworkRequests(nris);
+    }
+
+    private void clearNonDefaultNetworkAgents() {
+        // Copy mDefaultNetworkRequests to iterate and remove elements from it in
+        // handleRemoveNetworkRequest() without getting a ConcurrentModificationException.
+        final NetworkRequestInfo[] nris =
+                mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]);
+        for (final NetworkRequestInfo nri : nris) {
+            if (mDefaultRequest != nri) {
+                handleRemoveNetworkRequest(nri);
+            }
+        }
+    }
+
+    private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) {
+        mDefaultNetworkRequests.addAll(nris);
+        handleRegisterNetworkRequest(nris);
+    }
+
+    /**
+     * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}.
+     */
+    @VisibleForTesting
+    final class OemNetworkRequestFactory {
+        List<NetworkRequestInfo> createNrisFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final List<NetworkRequestInfo> nris = new ArrayList<>();
+            final SparseArray<Set<Integer>> uids =
+                    createUidsFromOemNetworkPreferences(preference);
+            for (int i = 0; i < uids.size(); i++) {
+                final int key = uids.keyAt(i);
+                final Set<Integer> value = uids.valueAt(i);
+                final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value);
+                // No need to add an nri without any requests.
+                if (0 == nri.mRequests.size()) {
+                    continue;
+                }
+                nris.add(nri);
+            }
+
+            return nris;
+        }
+
+        private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences(
+                @NonNull final OemNetworkPreferences preference) {
+            final SparseArray<Set<Integer>> uids = new SparseArray<>();
+            final PackageManager pm = mContext.getPackageManager();
+            for (final Map.Entry<String, Integer> entry :
+                    preference.getNetworkPreferences().entrySet()) {
+                @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue();
+                try {
+                    final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid;
+                    if (!uids.contains(pref)) {
+                        uids.put(pref, new ArraySet<>());
+                    }
+                    uids.get(pref).add(uid);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // Although this may seem like an error scenario, it is ok that uninstalled
+                    // packages are sent on a network preference as the system will watch for
+                    // package installations associated with this network preference and update
+                    // accordingly. This is done so as to minimize race conditions on app install.
+                    // TODO b/177092163 add app install watching.
+                    continue;
+                }
+            }
+            return uids;
+        }
+
+        private NetworkRequestInfo createNriFromOemNetworkPreferences(
+                @OemNetworkPreferences.OemNetworkPreference final int preference,
+                @NonNull final Set<Integer> uids) {
+            final List<NetworkRequest> requests = new ArrayList<>();
+            // Requests will ultimately be evaluated by order of insertion therefore it matters.
+            switch (preference) {
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    requests.add(createDefaultRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK:
+                    requests.add(createUnmeteredNetworkRequest());
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY:
+                    requests.add(createOemPaidNetworkRequest());
+                    break;
+                case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY:
+                    requests.add(createOemPrivateNetworkRequest());
+                    break;
+                default:
+                    // This should never happen.
+                    throw new IllegalArgumentException("createNriFromOemNetworkPreferences()"
+                            + " called with invalid preference of " + preference);
+            }
+
+            setOemNetworkRequestUids(requests, uids);
+            return new NetworkRequestInfo(requests);
+        }
+
+        private NetworkRequest createUnmeteredNetworkRequest() {
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_NOT_METERED)
+                    .addCapability(NET_CAPABILITY_VALIDATED);
+            return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap);
+        }
+
+        private NetworkRequest createOemPaidNetworkRequest() {
+            // NET_CAPABILITY_OEM_PAID is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PAID)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkRequest createOemPrivateNetworkRequest() {
+            // NET_CAPABILITY_OEM_PRIVATE is a restricted capability.
+            final NetworkCapabilities netcap = createDefaultPerAppNetCap()
+                    .addCapability(NET_CAPABILITY_OEM_PRIVATE)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+            return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap);
+        }
+
+        private NetworkCapabilities createDefaultPerAppNetCap() {
+            final NetworkCapabilities netCap = new NetworkCapabilities();
+            netCap.addCapability(NET_CAPABILITY_INTERNET);
+            netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+            return netCap;
+        }
+
+        private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests,
+                @NonNull final Set<Integer> uids) {
+            final Set<UidRange> ranges = new ArraySet<>();
+            for (final int uid : uids) {
+                ranges.add(new UidRange(uid, uid));
+            }
+            for (final NetworkRequest req : requests) {
+                req.networkCapabilities.setUids(ranges);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index f648c3e..b48bc90 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -29,6 +29,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.InetAddresses;
@@ -41,6 +42,7 @@
 import android.net.IpSecTunnelInterfaceResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.TrafficStats;
 import android.net.util.NetdService;
@@ -797,9 +799,15 @@
         }
     }
 
-    private final class TunnelInterfaceRecord extends OwnedResourceRecord {
+    /**
+     * Tracks an tunnel interface, and manages cleanup paths.
+     *
+     * <p>This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock
+     */
+    @VisibleForTesting
+    final class TunnelInterfaceRecord extends OwnedResourceRecord {
         private final String mInterfaceName;
-        private final Network mUnderlyingNetwork;
 
         // outer addresses
         private final String mLocalAddress;
@@ -810,6 +818,8 @@
 
         private final int mIfId;
 
+        private Network mUnderlyingNetwork;
+
         TunnelInterfaceRecord(
                 int resourceId,
                 String interfaceName,
@@ -870,14 +880,22 @@
             releaseNetId(mOkey);
         }
 
-        public String getInterfaceName() {
-            return mInterfaceName;
+        @GuardedBy("IpSecService.this")
+        public void setUnderlyingNetwork(Network underlyingNetwork) {
+            // When #applyTunnelModeTransform is called, this new underlying network will be used to
+            // update the output mark of the input transform.
+            mUnderlyingNetwork = underlyingNetwork;
         }
 
+        @GuardedBy("IpSecService.this")
         public Network getUnderlyingNetwork() {
             return mUnderlyingNetwork;
         }
 
+        public String getInterfaceName() {
+            return mInterfaceName;
+        }
+
         /** Returns the local, outer address for the tunnelInterface */
         public String getLocalAddress() {
             return mLocalAddress;
@@ -1429,6 +1447,34 @@
         }
     }
 
+    /** Set TunnelInterface to use a specific underlying network. */
+    @Override
+    public synchronized void setNetworkForTunnelInterface(
+            int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+        final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        final ConnectivityManager connectivityManager =
+                mContext.getSystemService(ConnectivityManager.class);
+        final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+        if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+            throw new IllegalArgumentException(
+                    "Underlying network cannot be the network being exposed by this tunnel");
+        }
+
+        // It is meaningless to check if the network exists or is valid because the network might
+        // disconnect at any time after it passes the check.
+
+        tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+    }
+
     /**
      * Delete a TunnelInterface that has been been allocated by and registered with the system
      * server
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a6cfae4..e12586b 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,8 +1,8 @@
 # Connectivity / Networking
 per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
 
-# Vibrator / Threads
-per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
+# Threads
+per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 41903fc..67f6ec9 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -902,10 +902,13 @@
                 if (registeredObserver != null) {
                     Iterator<MonitoredPackage> it = failedPackages.iterator();
                     while (it.hasNext()) {
-                        VersionedPackage versionedPkg = it.next().mPackage;
-                        Slog.i(TAG, "Explicit health check failed for package " + versionedPkg);
-                        registeredObserver.execute(versionedPkg,
-                                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+                        VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
+                        if (versionedPkg != null) {
+                            Slog.i(TAG,
+                                    "Explicit health check failed for package " + versionedPkg);
+                            registeredObserver.execute(versionedPkg,
+                                    PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+                        }
                     }
                 }
             }
@@ -1342,11 +1345,7 @@
 
     MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
             boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
-        VersionedPackage pkg = getVersionedPackage(name);
-        if (pkg == null) {
-            return null;
-        }
-        return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs,
+        return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
                 hasPassedHealthCheck, mitigationCalls);
     }
 
@@ -1371,7 +1370,7 @@
      * instances of this class.
      */
     class MonitoredPackage {
-        private final VersionedPackage mPackage;
+        private final String mPackageName;
         // Times when package failures happen sorted in ascending order
         @GuardedBy("mLock")
         private final LongArrayQueue mFailureHistory = new LongArrayQueue();
@@ -1399,10 +1398,10 @@
         @GuardedBy("mLock")
         private long mHealthCheckDurationMs = Long.MAX_VALUE;
 
-        MonitoredPackage(VersionedPackage pkg, long durationMs,
+        MonitoredPackage(String packageName, long durationMs,
                 long healthCheckDurationMs, boolean hasPassedHealthCheck,
                 LongArrayQueue mitigationCalls) {
-            mPackage = pkg;
+            mPackageName = packageName;
             mDurationMs = durationMs;
             mHealthCheckDurationMs = healthCheckDurationMs;
             mHasPassedHealthCheck = hasPassedHealthCheck;
@@ -1556,7 +1555,7 @@
 
         /** Returns the monitored package name. */
         private String getName() {
-            return mPackage.getPackageName();
+            return mPackageName;
         }
 
         /**
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 00d8b0f..d10cf4d 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -147,14 +148,15 @@
     private int getAllowedUid(int userHandle) {
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
-        PackageManager pm = mContext.getPackageManager();
         int allowedUid = -1;
-        try {
-            allowedUid = pm.getPackageUidAsUser(allowedPackage,
-                    PackageManager.MATCH_SYSTEM_ONLY, userHandle);
-        } catch (PackageManager.NameNotFoundException e) {
-            // not expected
-            Slog.e(TAG, "not able to find package " + allowedPackage, e);
+        if (!TextUtils.isEmpty(allowedPackage)) {
+            try {
+                allowedUid = mContext.getPackageManager().getPackageUidAsUser(
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+            } catch (PackageManager.NameNotFoundException e) {
+                // not expected
+                Slog.e(TAG, "not able to find package " + allowedPackage, e);
+            }
         }
         return allowedUid;
     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1ad0176..2f98199 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -939,9 +939,12 @@
         if (transcodeEnabled) {
             LocalServices.getService(ActivityManagerInternal.class)
                     .registerAnrController((packageName, uid) -> {
-                        // TODO: Retrieve delay from ExternalStorageService that can check
-                        // transcoding status
-                        return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0);
+                        try {
+                            return mStorageSessionController.getAnrDelayMillis(packageName, uid);
+                        } catch (ExternalStorageServiceException e) {
+                            Log.e(TAG, "Failed to get ANR delay for " + packageName, e);
+                            return 0;
+                        }
                     });
         }
     }
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 7f638b9..915517a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -19,6 +19,7 @@
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
@@ -82,6 +83,7 @@
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
+import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
@@ -158,6 +160,7 @@
     private NotificationManager mNotificationManager;
     private StatusBarManager mStatusBarManager;
     private WindowManagerInternal mWindowManager;
+    private ActivityTaskManagerInternal mActivityTaskManager;
     private AlarmManager mAlarmManager;
     private PowerManager mPowerManager;
 
@@ -366,6 +369,7 @@
                 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                 mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+                mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
                 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
                 TwilightManager twilightManager = getLocalService(TwilightManager.class);
                 if (twilightManager != null) mTwilightManager = twilightManager;
@@ -750,6 +754,39 @@
         }
 
         @Override
+        public void setApplicationNightMode(@UiModeManager.NightMode int mode)
+                throws RemoteException {
+            switch (mode) {
+                case UiModeManager.MODE_NIGHT_NO:
+                case UiModeManager.MODE_NIGHT_YES:
+                case UiModeManager.MODE_NIGHT_AUTO:
+                case UiModeManager.MODE_NIGHT_CUSTOM:
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown mode: " + mode);
+            }
+            final int configNightMode;
+            switch (mode) {
+                case MODE_NIGHT_YES:
+                    configNightMode = Configuration.UI_MODE_NIGHT_YES;
+                    break;
+                case MODE_NIGHT_NO:
+                    configNightMode = Configuration.UI_MODE_NIGHT_NO;
+                    break;
+                default:
+                    configNightMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
+            }
+            try {
+                final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+                        mActivityTaskManager.createPackageConfigurationUpdater();
+                updater.setNightMode(configNightMode);
+                updater.commit();
+            } catch (RemoteException e) {
+                throw e;
+            }
+        }
+
+        @Override
         public boolean isUiModeLocked() {
             synchronized (mLock) {
                 return mUiModeLocked;
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 4dce59f..27210da 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -641,21 +641,34 @@
         }
 
         boolean isVcnManagedNetwork = false;
+        boolean isRestrictedCarrierWifi = false;
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             synchronized (mLock) {
                 ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
 
                 Vcn vcn = mVcns.get(subGroup);
-                if (vcn != null && vcn.isActive()) {
-                    isVcnManagedNetwork = true;
+                if (vcn != null) {
+                    if (vcn.isActive()) {
+                        isVcnManagedNetwork = true;
+                    }
+
+                    if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                        // Carrier WiFi always restricted if VCN exists (even in safe mode).
+                        isRestrictedCarrierWifi = true;
+                    }
                 }
             }
         }
+
         if (isVcnManagedNetwork) {
             networkCapabilities.removeCapability(
                     NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
         }
 
+        if (isRestrictedCarrierWifi) {
+            networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+
         return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
     }
 
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
deleted file mode 100644
index 2ac365d..0000000
--- a/services/core/java/com/android/server/VibratorService.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.vibrator.IVibrator;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.CombinedVibrationEffect;
-import android.os.ExternalVibration;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IExternalVibratorService;
-import android.os.IVibratorService;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-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.app.IBatteryStats;
-import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
-import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/** System implementation of {@link IVibratorService}. */
-public class VibratorService extends IVibratorService.Stub {
-    private static final String TAG = "VibratorService";
-    private static final boolean DEBUG = false;
-    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
-
-    // Default vibration attributes. Used when vibration is requested without attributes
-    private static final VibrationAttributes DEFAULT_ATTRIBUTES =
-            new VibrationAttributes.Builder().build();
-
-    // Used to generate globally unique vibration ids.
-    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
-    private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations;
-    private final LinkedList<Vibration.DebugInfo> mPreviousVibrations;
-    private final int mPreviousVibrationsLimit;
-    private final Handler mH;
-    private final Object mLock = new Object();
-    private final VibratorController mVibratorController;
-    private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
-
-    private final Context mContext;
-    private final PowerManager.WakeLock mWakeLock;
-    private final AppOpsManager mAppOps;
-    private final IBatteryStats mBatteryStatsService;
-    private final String mSystemUiPackage;
-    private VibrationSettings mVibrationSettings;
-    private VibrationScaler mVibrationScaler;
-    private InputDeviceDelegate mInputDeviceDelegate;
-
-    @GuardedBy("mLock")
-    private VibrationThread mThread;
-    @GuardedBy("mLock")
-    private VibrationThread mNextVibrationThread;
-
-    @GuardedBy("mLock")
-    private Vibration mCurrentVibration;
-    private int mCurVibUid = -1;
-    private ExternalVibrationHolder mCurrentExternalVibration;
-
-    /**
-     * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished
-     * vibrations.
-     */
-    private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
-
-        @Override
-        public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
-            return false;
-        }
-
-        @Override
-        public boolean triggerSyncedVibration(long vibrationId) {
-            return false;
-        }
-
-        @Override
-        public void cancelSyncedVibration() {
-        }
-
-        @Override
-        public void onVibrationEnded(long vibrationId, Vibration.Status status) {
-            if (DEBUG) {
-                Slog.d(TAG, "Vibration thread finished with status " + status);
-            }
-            synchronized (mLock) {
-                if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
-                    mThread = null;
-                    reportFinishVibrationLocked(status);
-                    if (mNextVibrationThread != null) {
-                        startVibrationThreadLocked(mNextVibrationThread);
-                        mNextVibrationThread = null;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service.
-     */
-    private static final class VibrationCompleteListener implements OnVibrationCompleteListener {
-        private WeakReference<VibratorService> mServiceRef;
-
-        VibrationCompleteListener(VibratorService service) {
-            mServiceRef = new WeakReference<>(service);
-        }
-
-        @Override
-        public void onComplete(int vibratorId, long vibrationId) {
-            VibratorService service = mServiceRef.get();
-            if (service != null) {
-                service.onVibrationComplete(vibratorId, vibrationId);
-            }
-        }
-    }
-
-    /** Holder for a {@link ExternalVibration}. */
-    private final class ExternalVibrationHolder {
-
-        public final ExternalVibration externalVibration;
-        public int scale;
-
-        private final long mStartTimeDebug;
-        private long mEndTimeDebug;
-        private Vibration.Status mStatus;
-
-        private ExternalVibrationHolder(ExternalVibration externalVibration) {
-            this.externalVibration = externalVibration;
-            this.scale = IExternalVibratorService.SCALE_NONE;
-            mStartTimeDebug = System.currentTimeMillis();
-            mStatus = Vibration.Status.RUNNING;
-        }
-
-        public void end(Vibration.Status status) {
-            if (mStatus != Vibration.Status.RUNNING) {
-                // Vibration already ended, keep first ending status set and ignore this one.
-                return;
-            }
-            mStatus = status;
-            mEndTimeDebug = System.currentTimeMillis();
-        }
-
-        public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(
-                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
-                    scale, externalVibration.getVibrationAttributes(),
-                    externalVibration.getUid(), externalVibration.getPackage(),
-                    /* reason= */ null, mStatus);
-        }
-    }
-
-    VibratorService(Context context) {
-        this(context, new Injector());
-    }
-
-    @VisibleForTesting
-    VibratorService(Context context, Injector injector) {
-        mH = injector.createHandler(Looper.myLooper());
-        mVibratorController = injector.createVibratorController(
-                new VibrationCompleteListener(this));
-
-        // Reset the hardware to a default state, in case this is a runtime
-        // restart instead of a fresh boot.
-        mVibratorController.off();
-
-        mContext = context;
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
-        mWakeLock.setReferenceCounted(true);
-
-        mAppOps = mContext.getSystemService(AppOpsManager.class);
-        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
-        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
-                .getSystemUiServiceComponent().getPackageName();
-
-        mPreviousVibrationsLimit = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-
-        mPreviousRingVibrations = new LinkedList<>();
-        mPreviousNotificationVibrations = new LinkedList<>();
-        mPreviousAlarmVibrations = new LinkedList<>();
-        mPreviousVibrations = new LinkedList<>();
-        mPreviousExternalVibrations = new LinkedList<>();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        context.registerReceiver(mIntentReceiver, filter);
-
-        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
-    }
-
-    public void systemReady() {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
-        try {
-            mVibrationSettings = new VibrationSettings(mContext, mH);
-            mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
-            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);
-
-            mVibrationSettings.addListener(this::updateVibrators);
-
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    mVibrationSettings.updateSettings();
-                }
-            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
-
-            updateVibrators();
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    /** Callback for when vibration is complete, to be called by native. */
-    @VisibleForTesting
-    public void onVibrationComplete(int vibratorId, long vibrationId) {
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
-                    && mThread != null) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
-                }
-                // Let the thread playing the vibration handle the callback, since it might be
-                // expecting the vibrator to turn off multiple times during a single vibration.
-                mThread.vibratorComplete(vibratorId);
-            }
-        }
-    }
-
-    @Override // Binder call
-    public boolean hasVibrator() {
-        // For now, we choose to ignore the presence of input devices that have vibrators
-        // when reporting whether the device has a vibrator.  Applications often use this
-        // information to decide whether to enable certain features so they expect the
-        // result of hasVibrator() to be constant.  For now, just report whether
-        // the device has a built-in vibrator.
-        return mVibratorController.isAvailable();
-    }
-
-    @Override // Binder call
-    public boolean isVibrating() {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.isVibrating();
-    }
-
-    @Override // Binder call
-    public VibratorInfo getVibratorInfo() {
-        return mVibratorController.getVibratorInfo();
-    }
-
-    @Override // Binder call
-    public boolean registerVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.registerVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    @GuardedBy("mLock")
-    public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
-        if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
-            throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
-        }
-        return mVibratorController.unregisterVibratorStateListener(listener);
-    }
-
-    @Override // Binder call
-    public boolean hasAmplitudeControl() {
-        // Input device vibrators always support amplitude controls.
-        return mInputDeviceDelegate.isAvailable()
-                || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
-    }
-
-    private void verifyIncomingUid(int uid) {
-        if (uid == Binder.getCallingUid()) {
-            return;
-        }
-        if (Binder.getCallingPid() == Process.myPid()) {
-            return;
-        }
-        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
-                Binder.getCallingPid(), Binder.getCallingUid(), null);
-    }
-
-    /**
-     * Validate the incoming VibrationEffect.
-     *
-     * We can't throw exceptions here since we might be called from some system_server component,
-     * which would bring the whole system down.
-     *
-     * @return whether the VibrationEffect is valid
-     */
-    private static boolean verifyVibrationEffect(VibrationEffect effect) {
-        if (effect == null) {
-            // Effect must not be null.
-            Slog.wtf(TAG, "effect must not be null");
-            return false;
-        }
-        try {
-            effect.validate();
-        } catch (Exception e) {
-            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
-            return false;
-        }
-        return true;
-    }
-
-    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Prebaked
-                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
-            return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
-                    fallback);
-        }
-        return effect;
-    }
-
-    private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
-        if (attrs == null) {
-            attrs = DEFAULT_ATTRIBUTES;
-        }
-        if (shouldBypassDnd(attrs)) {
-            if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-                    || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                    || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
-                final int flags = attrs.getFlags()
-                        & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
-                attrs = new VibrationAttributes.Builder(attrs)
-                                .setFlags(flags, attrs.getFlags()).build();
-            }
-        }
-
-        return attrs;
-    }
-
-    @Override // Binder call
-    public void vibrate(int uid, String opPkg, VibrationEffect effect,
-            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
-        try {
-            if (!hasPermission(android.Manifest.permission.VIBRATE)) {
-                throw new SecurityException("Requires VIBRATE permission");
-            }
-            if (token == null) {
-                Slog.e(TAG, "token must not be null");
-                return;
-            }
-            verifyIncomingUid(uid);
-            if (!verifyVibrationEffect(effect)) {
-                return;
-            }
-            effect = fixupVibrationEffect(effect);
-            attrs = fixupVibrationAttributes(attrs);
-            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(),
-                    CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason);
-
-            // If our current vibration is longer than the new vibration and is the same amplitude,
-            // then just let the current one finish.
-            synchronized (mLock) {
-                VibrationEffect currentEffect =
-                        mCurrentVibration == null ? null : getEffect(mCurrentVibration);
-                if (effect instanceof VibrationEffect.OneShot
-                        && currentEffect instanceof VibrationEffect.OneShot) {
-                    VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
-                    VibrationEffect.OneShot currentOneShot =
-                            (VibrationEffect.OneShot) currentEffect;
-                    if (currentOneShot.getDuration() > newOneShot.getDuration()
-                            && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
-                        if (DEBUG) {
-                            Slog.d(TAG,
-                                    "Ignoring incoming vibration in favor of current vibration");
-                        }
-                        endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING);
-                        return;
-                    }
-                }
-
-
-                // If something has external control of the vibrator, assume that it's more
-                // important for now.
-                if (mCurrentExternalVibration != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL);
-                    return;
-                }
-
-                // If the current vibration is repeating and the incoming one is non-repeating,
-                // then ignore the non-repeating vibration. This is so that we don't cancel
-                // vibrations that are meant to grab the attention of the user, like ringtones and
-                // alarms, in favor of one-shot vibrations that are likely quite short.
-                if (!isRepeatingVibration(effect)
-                        && mCurrentVibration != null
-                        && isRepeatingVibration(currentEffect)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
-                    }
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM);
-                    return;
-                }
-
-                if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) {
-                    Slog.e(TAG, "Ignoring incoming vibration as process with"
-                            + " uid= " + uid + " is background,"
-                            + " attrs= " + vib.attrs);
-                    endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND);
-                    return;
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    startVibrationLocked(vib);
-                    boolean isNextVibration = mNextVibrationThread != null
-                            && vib.equals(mNextVibrationThread.getVibration());
-
-                    if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
-                        // Vibration was unexpectedly ignored: add to list for debugging
-                        endVibrationLocked(vib, Vibration.Status.IGNORED);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    private boolean hasPermission(String permission) {
-        return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    private static boolean isRepeatingVibration(VibrationEffect effect) {
-        return effect.getDuration() == Long.MAX_VALUE;
-    }
-
-    private static <T extends VibrationEffect> T getEffect(Vibration vib) {
-        return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect();
-    }
-
-    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
-        final LinkedList<Vibration.DebugInfo> previousVibrations;
-        switch (vib.attrs.getUsage()) {
-            case VibrationAttributes.USAGE_NOTIFICATION:
-                previousVibrations = mPreviousNotificationVibrations;
-                break;
-            case VibrationAttributes.USAGE_RINGTONE:
-                previousVibrations = mPreviousRingVibrations;
-                break;
-            case VibrationAttributes.USAGE_ALARM:
-                previousVibrations = mPreviousAlarmVibrations;
-                break;
-            default:
-                previousVibrations = mPreviousVibrations;
-        }
-        if (previousVibrations.size() > mPreviousVibrationsLimit) {
-            previousVibrations.removeFirst();
-        }
-        vib.end(status);
-        previousVibrations.addLast(vib.getDebugInfo());
-    }
-
-    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
-        if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
-            mPreviousExternalVibrations.removeFirst();
-        }
-        vib.end(status);
-        mPreviousExternalVibrations.addLast(vib.getDebugInfo());
-    }
-
-    @Override // Binder call
-    public void cancelVibrate(IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.VIBRATE,
-                "cancelVibrate");
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.token == token) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Canceling vibration.");
-                }
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void doCancelVibrateLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
-        try {
-            if (mThread != null) {
-                mThread.cancel();
-            }
-            mInputDeviceDelegate.cancelVibrateIfAvailable();
-            if (mCurrentExternalVibration != null) {
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.mute();
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationLocked(final Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
-        try {
-            if (!shouldVibrate(vib)) {
-                return;
-            }
-            applyVibrationIntensityScalingLocked(vib);
-            startVibrationInnerLocked(vib);
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationInnerLocked(Vibration vib) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
-        try {
-            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
-                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
-            if (inputDevicesAvailable) {
-                endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
-            } else if (mThread == null) {
-                startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks));
-            } else {
-                mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks);
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void startVibrationThreadLocked(VibrationThread thread) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-        mCurrentVibration = thread.getVibration();
-        mThread = thread;
-        mThread.start();
-    }
-
-    /** Scale the vibration effect by the intensity as appropriate based its intent. */
-    private void applyVibrationIntensityScalingLocked(Vibration vib) {
-        vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
-    }
-
-    private static boolean shouldBypassDnd(VibrationAttributes attrs) {
-        return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
-    }
-
-    private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
-        int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                attrs.getAudioUsage(), uid, packageName);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
-        }
-
-        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) {
-            // If we're just ignoring the vibration op then this is set by DND and we should ignore
-            // if we're asked to bypass. AppOps won't be able to record this operation, so make
-            // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
-            mode = AppOpsManager.MODE_ALLOWED;
-        }
-        return mode;
-    }
-
-    private boolean shouldVibrate(Vibration vib) {
-        if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER);
-            return false;
-        }
-
-        int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage());
-        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
-            endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS);
-            return false;
-        }
-
-        if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) {
-            if (DEBUG) {
-                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
-            }
-            endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE);
-            return false;
-        }
-
-        final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs);
-        if (mode != AppOpsManager.MODE_ALLOWED) {
-            if (mode == AppOpsManager.MODE_ERRORED) {
-                // We might be getting calls from within system_server, so we don't actually
-                // want to throw a SecurityException here.
-                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
-                endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS);
-            } else {
-                endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS);
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    @GuardedBy("mLock")
-    private void reportFinishVibrationLocked(Vibration.Status status) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
-        try {
-            if (mCurrentVibration != null) {
-                endVibrationLocked(mCurrentVibration, status);
-                mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
-                        mCurrentVibration.opPkg);
-
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                mCurrentVibration = null;
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
-    @VisibleForTesting
-    void updateVibrators() {
-        synchronized (mLock) {
-            boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
-                    mVibrationSettings.shouldVibrateInputDevices());
-
-            if (mCurrentVibration == null) {
-                return;
-            }
-
-            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
-                    mCurrentVibration.attrs.getUsage())) {
-                // If the state changes out from under us then just reset.
-                doCancelVibrateLocked(Vibration.Status.CANCELLED);
-            }
-        }
-    }
-
-    private boolean isSystemHapticFeedback(Vibration vib) {
-        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
-            return false;
-        }
-        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
-    }
-
-    private void dumpInternal(PrintWriter pw) {
-        pw.println("Vibrator Service:");
-        synchronized (mLock) {
-            pw.print("  mCurrentVibration=");
-            if (mCurrentVibration != null) {
-                pw.println(mCurrentVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.print("  mCurrentExternalVibration=");
-            if (mCurrentExternalVibration != null) {
-                pw.println(mCurrentExternalVibration.getDebugInfo().toString());
-            } else {
-                pw.println("null");
-            }
-            pw.println("  mVibratorController=" + mVibratorController);
-            pw.println("  mVibrationSettings=" + mVibrationSettings);
-            pw.println();
-            pw.println("  Previous ring vibrations:");
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                pw.print("    ");
-                pw.println(info.toString());
-            }
-
-            pw.println("  Previous notification vibrations:");
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous alarm vibrations:");
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous vibrations:");
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                pw.println("    " + info);
-            }
-
-            pw.println("  Previous external vibrations:");
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                pw.println("    " + info);
-            }
-        }
-    }
-
-    private void dumpProto(FileDescriptor fd) {
-        final ProtoOutputStream proto = new ProtoOutputStream(fd);
-
-        synchronized (mLock) {
-            if (mCurrentVibration != null) {
-                mCurrentVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_VIBRATION);
-            }
-            if (mCurrentExternalVibration != null) {
-                mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                        VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
-            }
-            proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
-            proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
-                    mVibratorController.isUnderExternalControl());
-            mVibrationSettings.dumpProto(proto);
-
-            for (Vibration.DebugInfo info : mPreviousRingVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousNotificationVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousAlarmVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
-            }
-
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
-            }
-        }
-        proto.flush();
-    }
-
-    /** Point of injection for test dependencies */
-    @VisibleForTesting
-    static class Injector {
-
-        VibratorController createVibratorController(OnVibrationCompleteListener listener) {
-            return new VibratorController(/* vibratorId= */ -1, listener);
-        }
-
-        Handler createHandler(Looper looper) {
-            return new Handler(looper);
-        }
-
-        void addService(String name, IBinder service) {
-            ServiceManager.addService(name, service);
-        }
-    }
-
-    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mLock) {
-                    // When the system is entering a non-interactive state, we want
-                    // to cancel vibrations in case a misbehaving app has abandoned
-                    // them.  However it may happen that the system is currently playing
-                    // haptic feedback as part of the transition.  So we don't cancel
-                    // system vibrations.
-                    if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
-                        mNextVibrationThread = null;
-                        doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    };
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
-
-        final long ident = Binder.clearCallingIdentity();
-
-        boolean isDumpProto = false;
-        for (String arg : args) {
-            if (arg.equals("--proto")) {
-                isDumpProto = true;
-            }
-        }
-        try {
-            if (isDumpProto) {
-                dumpProto(fd);
-            } else {
-                dumpInternal(pw);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
-        new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
-    }
-
-    final class ExternalVibratorService extends IExternalVibratorService.Stub {
-        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
-
-        @Override
-        public int onExternalVibrationStart(ExternalVibration vib) {
-            if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
-                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
-                        + " tried to play externally controlled vibration"
-                        + " without VIBRATE permission, ignoring.");
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
-                vibHolder.scale = SCALE_MUTE;
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
-                } else {
-                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
-                }
-                return IExternalVibratorService.SCALE_MUTE;
-            }
-
-            VibrationThread cancelingVibration = null;
-            int scale;
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    // We are already playing this external vibration, so we can return the same
-                    // scale calculated in the previous call to this method.
-                    return mCurrentExternalVibration.scale;
-                }
-                if (mCurrentExternalVibration == null) {
-                    // If we're not under external control right now, then cancel any normal
-                    // vibration that may be playing and ready the vibrator for external control.
-                    mNextVibrationThread = null;
-                    doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    cancelingVibration = mThread;
-                } else {
-                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
-                }
-                // At this point we either have an externally controlled vibration playing, or
-                // no vibration playing. Since the interface defines that only one externally
-                // controlled vibration can play at a time, by returning something other than
-                // SCALE_MUTE from this function we can be assured that if we are currently
-                // playing vibration, it will be muted in favor of the new vibration.
-                //
-                // Note that this doesn't support multiple concurrent external controls, as we
-                // would need to mute the old one still if it came from a different controller.
-                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
-                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
-                vib.linkToDeath(mCurrentExternalDeathRecipient);
-                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
-                        vib.getVibrationAttributes().getUsage());
-                scale = mCurrentExternalVibration.scale;
-            }
-            if (cancelingVibration != null) {
-                try {
-                    cancelingVibration.join();
-                } catch (InterruptedException e) {
-                    Slog.w("Interrupted while waiting current vibration to be cancelled before "
-                            + "starting external vibration", e);
-                }
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Vibrator going under external control.");
-            }
-            mVibratorController.setExternalControl(true);
-            if (DEBUG) {
-                Slog.e(TAG, "Playing external vibration: " + vib);
-            }
-            return scale;
-        }
-
-        @Override
-        public void onExternalVibrationStop(ExternalVibration vib) {
-            synchronized (mLock) {
-                if (mCurrentExternalVibration != null
-                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
-                    if (DEBUG) {
-                        Slog.e(TAG, "Stopping external vibration" + vib);
-                    }
-                    doCancelExternalVibrateLocked(Vibration.Status.FINISHED);
-                }
-            }
-        }
-
-        private void doCancelExternalVibrateLocked(Vibration.Status status) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
-            try {
-                if (mCurrentExternalVibration == null) {
-                    return;
-                }
-                endVibrationLocked(mCurrentExternalVibration, status);
-                mCurrentExternalVibration.externalVibration.unlinkToDeath(
-                        mCurrentExternalDeathRecipient);
-                mCurrentExternalDeathRecipient = null;
-                mCurrentExternalVibration = null;
-                mVibratorController.setExternalControl(false);
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
-            public void binderDied() {
-                synchronized (mLock) {
-                    if (mCurrentExternalVibration != null) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "External vibration finished because binder died");
-                        }
-                        doCancelExternalVibrateLocked(Vibration.Status.CANCELLED);
-                    }
-                }
-            }
-        }
-    }
-
-    private final class VibratorShellCommand extends ShellCommand {
-
-        private final IBinder mToken;
-
-        private final class CommonOptions {
-            public boolean force = false;
-            public void check(String opt) {
-                switch (opt) {
-                    case "-f":
-                        force = true;
-                        break;
-                }
-            }
-        }
-
-        private VibratorShellCommand(IBinder token) {
-            mToken = token;
-        }
-
-        @Override
-        public int onCommand(String cmd) {
-            if ("vibrate".equals(cmd)) {
-                return runVibrate();
-            } else if ("waveform".equals(cmd)) {
-                return runWaveform();
-            } else if ("prebaked".equals(cmd)) {
-                return runPrebaked();
-            } else if ("capabilities".equals(cmd)) {
-                return runCapabilities();
-            } else if ("cancel".equals(cmd)) {
-                cancelVibrate(mToken);
-                return 0;
-            }
-            return handleDefaultCommands(cmd);
-        }
-
-        private boolean checkDoNotDisturb(CommonOptions opts) {
-            if (mVibrationSettings.isInZenMode() && !opts.force) {
-                try (PrintWriter pw = getOutPrintWriter();) {
-                    pw.print("Ignoring because device is on DND mode ");
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private int runVibrate() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    commonOptions.check(opt);
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final long duration = Long.parseLong(getNextArgRequired());
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect =
-                        VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runWaveform() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform");
-            try {
-                String description = "Shell command";
-                int repeat = -1;
-                ArrayList<Integer> amplitudesList = null;
-                CommonOptions commonOptions = new CommonOptions();
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    switch (opt) {
-                        case "-d":
-                            description = getNextArgRequired();
-                            break;
-                        case "-r":
-                            repeat = Integer.parseInt(getNextArgRequired());
-                            break;
-                        case "-a":
-                            if (amplitudesList == null) {
-                                amplitudesList = new ArrayList<Integer>();
-                            }
-                            break;
-                        default:
-                            commonOptions.check(opt);
-                            break;
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                ArrayList<Long> timingsList = new ArrayList<Long>();
-
-                String arg;
-                while ((arg = getNextArg()) != null) {
-                    if (amplitudesList != null && amplitudesList.size() < timingsList.size()) {
-                        amplitudesList.add(Integer.parseInt(arg));
-                    } else {
-                        timingsList.add(Long.parseLong(arg));
-                    }
-                }
-
-                VibrationEffect effect;
-                long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray();
-                if (amplitudesList == null) {
-                    effect = VibrationEffect.createWaveform(timings, repeat);
-                } else {
-                    int[] amplitudes =
-                            amplitudesList.stream().mapToInt(Integer::intValue).toArray();
-                    effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
-                }
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runPrebaked() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked");
-            try {
-                CommonOptions commonOptions = new CommonOptions();
-                boolean shouldFallback = false;
-
-                String opt;
-                while ((opt = getNextOption()) != null) {
-                    if ("-b".equals(opt)) {
-                        shouldFallback = true;
-                    } else {
-                        commonOptions.check(opt);
-                    }
-                }
-
-                if (checkDoNotDisturb(commonOptions)) {
-                    return 0;
-                }
-
-                final int id = Integer.parseInt(getNextArgRequired());
-
-                String description = getNextArg();
-                if (description == null) {
-                    description = "Shell command";
-                }
-
-                VibrationEffect effect = VibrationEffect.get(id, shouldFallback);
-                VibrationAttributes attrs = createVibrationAttributes(commonOptions);
-                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
-                        mToken);
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private int runCapabilities() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities");
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator capabilities:");
-                if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                    pw.println("  Always on effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-                    pw.println("  Compose effects");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
-                    pw.println("  Amplitude control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                    pw.println("  External control");
-                }
-                if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) {
-                    pw.println("  External amplitude control");
-                }
-                pw.println("");
-                return 0;
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
-            final int flags = commonOptions.force
-                    ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
-                    : 0;
-            return new VibrationAttributes.Builder()
-                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
-                    // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
-                    .setUsage(VibrationAttributes.USAGE_TOUCH)
-                    .build();
-        }
-
-        @Override
-        public void onHelp() {
-            try (PrintWriter pw = getOutPrintWriter();) {
-                pw.println("Vibrator commands:");
-                pw.println("  help");
-                pw.println("    Prints this help text.");
-                pw.println("");
-                pw.println("  vibrate duration [description]");
-                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
-                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("  waveform [-d description] [-r index] [-a] duration [amplitude] ...");
-                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
-                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
-                pw.println("    user setting will be used to scale amplitude.");
-                pw.println("    If -r is provided, the waveform loops back to the specified");
-                pw.println("    index (e.g. 0 loops from the beginning)");
-                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
-                pw.println("    otherwise, it accepts durations only and alternates off/on");
-                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
-                pw.println("  prebaked [-b] effect-id [description]");
-                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
-                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
-                pw.println("    will be used to scale amplitude.");
-                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
-                pw.println("    the device doesn't support the given effect-id.");
-                pw.println("  capabilities");
-                pw.println("    Prints capabilities of this device.");
-                pw.println("  cancel");
-                pw.println("    Cancels any active vibration");
-                pw.println("Common Options:");
-                pw.println("  -f - Force. Ignore Do Not Disturb setting.");
-                pw.println("");
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
new file mode 100644
index 0000000..5d89bf1
--- /dev/null
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -0,0 +1,918 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.NETWORK_STACK;
+
+import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.IVpnManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkStack;
+import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
+import android.net.VpnManager;
+import android.net.VpnService;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.LockdownVpnTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs.
+ * @hide
+ */
+public class VpnManagerService extends IVpnManager.Stub {
+    private static final String TAG = VpnManagerService.class.getSimpleName();
+
+    @VisibleForTesting
+    protected final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+
+    private final Context mContext;
+    private final Context mUserAllContext;
+
+    private final Dependencies mDeps;
+
+    private final ConnectivityManager mCm;
+    private final KeyStore mKeyStore;
+    private final INetworkManagementService mNMS;
+    private final INetd mNetd;
+    private final UserManager mUserManager;
+
+    @VisibleForTesting
+    @GuardedBy("mVpns")
+    protected final SparseArray<Vpn> mVpns = new SparseArray<>();
+
+    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+    // a direct call to LockdownVpnTracker.isEnabled().
+    @GuardedBy("mVpns")
+    private boolean mLockdownEnabled;
+    @GuardedBy("mVpns")
+    private LockdownVpnTracker mLockdownTracker;
+
+    /**
+     * Dependencies of VpnManager, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Returns the calling UID of an IPC. */
+        public int getCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        /** Creates a HandlerThread to be used by this class. */
+        public HandlerThread makeHandlerThread() {
+            return new HandlerThread("VpnManagerService");
+        }
+
+        /** Returns the KeyStore instance to be used by this class. */
+        public KeyStore getKeyStore() {
+            return KeyStore.getInstance();
+        }
+
+        public INetd getNetd() {
+            return NetdService.getInstance();
+        }
+
+        public INetworkManagementService getINetworkManagementService() {
+            return INetworkManagementService.Stub.asInterface(
+                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+        }
+    }
+
+    public VpnManagerService(Context context, Dependencies deps) {
+        mContext = context;
+        mDeps = deps;
+        mHandlerThread = mDeps.makeHandlerThread();
+        mHandlerThread.start();
+        mHandler = mHandlerThread.getThreadHandler();
+        mKeyStore = mDeps.getKeyStore();
+        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mNMS = mDeps.getINetworkManagementService();
+        mNetd = mDeps.getNetd();
+        mUserManager = mContext.getSystemService(UserManager.class);
+        registerReceivers();
+        log("VpnManagerService starting up");
+    }
+
+    /** Creates a new VpnManagerService */
+    public static VpnManagerService create(Context context) {
+        return new VpnManagerService(context, new Dependencies());
+    }
+
+    /** Informs the service that the system is ready. */
+    public void systemReady() {
+        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
+        // for user to unlock device too.
+        updateLockdownVpn();
+    }
+
+    @Override
+    /** Dumps service state. */
+    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+            @Nullable String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println("VPNs:");
+        pw.increaseIndent();
+        synchronized (mVpns) {
+            for (int i = 0; i < mVpns.size(); i++) {
+                pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
+     * Prepare for a VPN application.
+     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param oldPackage Package name of the application which currently controls VPN, which will
+     *                   be replaced. If there is no such application, this should should either be
+     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
+     * @param newPackage Package name of the application which should gain control of VPN, or
+     *                   {@code null} to disable.
+     * @param userId User for whom to prepare the new VPN.
+     *
+     * @hide
+     */
+    @Override
+    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+            int userId) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
+     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
+     * class. If the caller is not {@code userId}, {@link
+     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param packageName The package for which authorization state should change.
+     * @param userId User for whom {@code packageName} is installed.
+     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
+     *     permissions should be granted. When unauthorizing an app, {@link
+     *     VpnManager.TYPE_VPN_NONE} should be used.
+     * @hide
+     */
+    @Override
+    public void setVpnPackageAuthorization(
+            String packageName, int userId, @VpnManager.VpnType int vpnType) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                vpn.setPackageAuthorization(packageName, vpnType);
+            }
+        }
+    }
+
+    /**
+     * Configure a TUN interface and return its file descriptor. Parameters
+     * are encoded and opaque to this class. This method is used by VpnBuilder
+     * and not available in VpnManager. Permissions are checked in
+     * Vpn class.
+     * @hide
+     */
+    @Override
+    public ParcelFileDescriptor establishVpn(VpnConfig config) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).establish(config);
+        }
+    }
+
+    @Override
+    public boolean addVpnAddress(String address, int prefixLength) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).addAddress(address, prefixLength);
+        }
+    }
+
+    @Override
+    public boolean removeVpnAddress(String address, int prefixLength) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            return mVpns.get(user).removeAddress(address, prefixLength);
+        }
+    }
+
+    @Override
+    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        final boolean success;
+        synchronized (mVpns) {
+            success = mVpns.get(user).setUnderlyingNetworks(networks);
+        }
+        return success;
+    }
+
+    /**
+     * Stores the given VPN profile based on the provisioning package name.
+     *
+     * <p>If there is already a VPN profile stored for the provisioning package, this call will
+     * overwrite the profile.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @return {@code true} if user consent has already been granted, {@code false} otherwise.
+     * @hide
+     */
+    @Override
+    public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
+        }
+    }
+
+    /**
+     * Deletes the stored VPN profile for the provisioning package
+     *
+     * <p>If there are no profiles for the given package, this method will silently succeed.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @hide
+     */
+    @Override
+    public void deleteVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
+        }
+    }
+
+    /**
+     * Starts the VPN based on the stored profile for the given package
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @throws IllegalArgumentException if no profile was found for the given package name.
+     * @hide
+     */
+    @Override
+    public void startVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            mVpns.get(user).startVpnProfile(packageName, mKeyStore);
+        }
+    }
+
+    /**
+     * Stops the Platform VPN if the provided package is running one.
+     *
+     * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
+     * exclusively by the Settings app, and passed into the platform at startup time.
+     *
+     * @hide
+     */
+    @Override
+    public void stopVpnProfile(@NonNull String packageName) {
+        final int user = UserHandle.getUserId(mDeps.getCallingUid());
+        synchronized (mVpns) {
+            mVpns.get(user).stopVpnProfile(packageName);
+        }
+    }
+
+    /**
+     * Start legacy VPN, controlling native daemons as needed. Creates a
+     * secondary thread to perform connection work, returning quickly.
+     */
+    @Override
+    public void startLegacyVpn(VpnProfile profile) {
+        int user = UserHandle.getUserId(mDeps.getCallingUid());
+        final LinkProperties egress = mCm.getActiveLinkProperties();
+        if (egress == null) {
+            throw new IllegalStateException("Missing active network connection");
+        }
+        synchronized (mVpns) {
+            throwIfLockdownEnabled();
+            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
+        }
+    }
+
+    /**
+     * Return the information of the ongoing legacy VPN. This method is used
+     * by VpnSettings and not available in ConnectivityManager. Permissions
+     * are checked in Vpn class.
+     */
+    @Override
+    public LegacyVpnInfo getLegacyVpnInfo(int userId) {
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            return mVpns.get(userId).getLegacyVpnInfo();
+        }
+    }
+
+    /**
+     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
+     * VpnDialogs and not available in ConnectivityManager.
+     * Permissions are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public VpnConfig getVpnConfig(int userId) {
+        enforceCrossUserPermission(userId);
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn != null) {
+                return vpn.getVpnConfig();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    private boolean isLockdownVpnEnabled() {
+        return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
+    }
+
+    @Override
+    public boolean updateLockdownVpn() {
+        // Allow the system UID for the system server and for Settings.
+        // Also, for unit tests, allow the process that ConnectivityService is running in.
+        if (mDeps.getCallingUid() != Process.SYSTEM_UID
+                && Binder.getCallingPid() != Process.myPid()) {
+            logw("Lockdown VPN only available to system process or AID_SYSTEM");
+            return false;
+        }
+
+        synchronized (mVpns) {
+            // Tear down existing lockdown if profile was removed
+            mLockdownEnabled = isLockdownVpnEnabled();
+            if (!mLockdownEnabled) {
+                setLockdownTracker(null);
+                return true;
+            }
+
+            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+            if (profileTag == null) {
+                loge("Lockdown VPN configured but cannot be read from keystore");
+                return false;
+            }
+            String profileName = new String(profileTag);
+            final VpnProfile profile = VpnProfile.decode(
+                    profileName, mKeyStore.get(Credentials.VPN + profileName));
+            if (profile == null) {
+                loge("Lockdown VPN configured invalid profile " + profileName);
+                setLockdownTracker(null);
+                return true;
+            }
+            int user = UserHandle.getUserId(mDeps.getCallingUid());
+            Vpn vpn = mVpns.get(user);
+            if (vpn == null) {
+                logw("VPN for user " + user + " not ready yet. Skipping lockdown");
+                return false;
+            }
+            setLockdownTracker(
+                    new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn,  profile));
+        }
+
+        return true;
+    }
+
+    /**
+     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+     */
+    @GuardedBy("mVpns")
+    private void setLockdownTracker(LockdownVpnTracker tracker) {
+        // Shutdown any existing tracker
+        final LockdownVpnTracker existing = mLockdownTracker;
+        // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
+        // necessary onBlockedStatusChanged callbacks.
+        mLockdownTracker = null;
+        if (existing != null) {
+            existing.shutdown();
+        }
+
+        if (tracker != null) {
+            mLockdownTracker = tracker;
+            mLockdownTracker.init();
+        }
+    }
+
+    /**
+     * Throws if there is any currently running, always-on Legacy VPN.
+     *
+     * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is
+     * running across the entire system. Tracking for app-based VPNs is done on a per-user,
+     * per-package basis in Vpn.java
+     */
+    @GuardedBy("mVpns")
+    private void throwIfLockdownEnabled() {
+        if (mLockdownEnabled) {
+            throw new IllegalStateException("Unavailable in lockdown mode");
+        }
+    }
+
+    /**
+     * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform
+     * some setup and then call {@code establish()} to connect.
+     *
+     * @return {@code true} if the service was started, the service was already connected, or there
+     *         was no always-on VPN to start. {@code false} otherwise.
+     */
+    private boolean startAlwaysOnVpn(int userId) {
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                // Shouldn't happen as all code paths that point here should have checked the Vpn
+                // exists already.
+                Log.wtf(TAG, "User " + userId + " has no Vpn configuration");
+                return false;
+            }
+
+            return vpn.startAlwaysOnVpn(mKeyStore);
+        }
+    }
+
+    @Override
+    public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+        enforceSettingsPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
+        }
+    }
+
+    @Override
+    public boolean setAlwaysOnVpnPackage(
+            int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+            if (isLockdownVpnEnabled()) {
+                return false;
+            }
+
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
+                return false;
+            }
+            if (!startAlwaysOnVpn(userId)) {
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String getAlwaysOnVpnPackage(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getAlwaysOnPackage();
+        }
+    }
+
+    @Override
+    public boolean isVpnLockdownEnabled(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            return vpn.getLockdown();
+        }
+    }
+
+    @Override
+    public List<String> getVpnLockdownAllowlist(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                logw("User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getLockdownAllowlist();
+        }
+    }
+
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner() {
+        return getVpnIfOwner(mDeps.getCallingUid());
+    }
+
+    // TODO: stop calling into Vpn.java and get this information from data in this class.
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner(int uid) {
+        final int user = UserHandle.getUserId(uid);
+
+        final Vpn vpn = mVpns.get(user);
+        if (vpn == null) {
+            return null;
+        } else {
+            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
+            return (info == null || info.ownerUid != uid) ? null : vpn;
+        }
+    }
+
+    private void registerReceivers() {
+        // Set up the listener for user state for creating user VPNs.
+        // Should run on mHandler to avoid any races.
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_STARTED);
+        intentFilter.addAction(Intent.ACTION_USER_STOPPED);
+        intentFilter.addAction(Intent.ACTION_USER_ADDED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+
+        mUserAllContext.registerReceiver(
+                mIntentReceiver,
+                intentFilter,
+                null /* broadcastPermission */,
+                mHandler);
+        mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
+                mUserPresentReceiver,
+                new IntentFilter(Intent.ACTION_USER_PRESENT),
+                null /* broadcastPermission */,
+                mHandler /* scheduler */);
+
+        // Listen to package add and removal events for all users.
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mUserAllContext.registerReceiver(
+                mIntentReceiver,
+                intentFilter,
+                null /* broadcastPermission */,
+                mHandler);
+
+        // Listen to lockdown VPN reset.
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
+        mUserAllContext.registerReceiver(
+                mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
+    }
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ensureRunningOnHandlerThread();
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+            final Uri packageData = intent.getData();
+            final String packageName =
+                    packageData != null ? packageData.getSchemeSpecificPart() : null;
+
+            if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
+                onVpnLockdownReset();
+            }
+
+            // UserId should be filled for below intents, check the existence.
+            if (userId == UserHandle.USER_NULL) return;
+
+            if (Intent.ACTION_USER_STARTED.equals(action)) {
+                onUserStarted(userId);
+            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+                onUserStopped(userId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                onUserAdded(userId);
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(userId);
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                onUserUnlocked(userId);
+            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                onPackageReplaced(packageName, uid);
+            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                final boolean isReplacing = intent.getBooleanExtra(
+                        Intent.EXTRA_REPLACING, false);
+                onPackageRemoved(packageName, uid, isReplacing);
+            } else {
+                Log.wtf(TAG, "received unexpected intent: " + action);
+            }
+        }
+    };
+
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ensureRunningOnHandlerThread();
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            updateLockdownVpn();
+            // Use the same context that registered receiver before to unregister it. Because use
+            // different context to unregister receiver will cause exception.
+            context.unregisterReceiver(this);
+        }
+    };
+
+    private void onUserStarted(int userId) {
+        synchronized (mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn != null) {
+                loge("Starting user already has a VPN");
+                return;
+            }
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
+            mVpns.put(userId, userVpn);
+            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+                updateLockdownVpn();
+            }
+        }
+    }
+
+    private void onUserStopped(int userId) {
+        synchronized (mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn == null) {
+                loge("Stopped user has no VPN");
+                return;
+            }
+            userVpn.onUserStopped();
+            mVpns.delete(userId);
+        }
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getAlwaysOn();
+        }
+    }
+
+    @Override
+    public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+        synchronized (mVpns) {
+            Vpn vpn = getVpnIfOwner();
+            return vpn != null && vpn.getLockdown();
+        }
+    }
+
+
+    private void onUserAdded(int userId) {
+        synchronized (mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserAdded(userId);
+            }
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        synchronized (mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserRemoved(userId);
+            }
+        }
+    }
+
+    private void onPackageReplaced(String packageName, int uid) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+                log("Restarting always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.startAlwaysOnVpn(mKeyStore);
+            }
+        }
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+        if (TextUtils.isEmpty(packageName) || uid < 0) {
+            Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
+            return;
+        }
+
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mVpns) {
+            final Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                return;
+            }
+            // Legacy always-on VPN won't be affected since the package name is not set.
+            if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+                log("Removing always-on VPN package " + packageName + " for user "
+                        + userId);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
+            }
+        }
+    }
+
+    private void onUserUnlocked(int userId) {
+        synchronized (mVpns) {
+            // User present may be sent because of an unlock, which might mean an unlocked keystore.
+            if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
+                updateLockdownVpn();
+            } else {
+                startAlwaysOnVpn(userId);
+            }
+        }
+    }
+
+    private void onVpnLockdownReset() {
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) mLockdownTracker.reset();
+        }
+    }
+
+
+    @Override
+    public void factoryReset() {
+        enforceSettingsPermission();
+
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)
+                || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
+            return;
+        }
+
+        // Remove always-on package
+        final int userId = UserHandle.getCallingUserId();
+        synchronized (mVpns) {
+            final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
+            if (alwaysOnPackage != null) {
+                setAlwaysOnVpnPackage(userId, null, false, null);
+                setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
+            }
+
+            // Turn Always-on VPN off
+            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                    mLockdownEnabled = false;
+                    setLockdownTracker(null);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            // Turn VPN off
+            VpnConfig vpnConfig = getVpnConfig(userId);
+            if (vpnConfig != null) {
+                if (vpnConfig.legacy) {
+                    prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+                } else {
+                    // Prevent this app (packagename = vpnConfig.user) from initiating
+                    // VPN connections in the future without user intervention.
+                    setVpnPackageAuthorization(
+                            vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
+
+                    prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+                }
+            }
+        }
+    }
+
+    private void ensureRunningOnHandlerThread() {
+        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on VpnManagerService thread: "
+                            + Thread.currentThread().getName());
+        }
+    }
+
+    private void enforceControlAlwaysOnVpnPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
+                "VpnManagerService");
+    }
+
+    /**
+     * Require that the caller is either in the same user or has appropriate permission to interact
+     * across users.
+     *
+     * @param userId Target user for whatever operation the current IPC is supposed to perform.
+     */
+    private void enforceCrossUserPermission(int userId) {
+        if (userId == UserHandle.getCallingUserId()) {
+            // Not a cross-user call.
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                "VpnManagerService");
+    }
+
+    private void enforceSettingsPermission() {
+        enforceAnyPermissionOf(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
+    private static void log(String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void logw(String s) {
+        Log.w(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS
index ea5fd36..8dcc04a 100644
--- a/services/core/java/com/android/server/accounts/OWNERS
+++ b/services/core/java/com/android/server/accounts/OWNERS
@@ -3,7 +3,6 @@
 sandrakwan@google.com
 hackbod@google.com
 svetoslavganov@google.com
-moltmann@google.com
 fkupolov@google.com
 yamasani@google.com
 omakoto@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9382e1a..0b1c115 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -186,6 +186,7 @@
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -1490,7 +1491,9 @@
     private static final int INDEX_TOTAL_SWAP_PSS = 10;
     private static final int INDEX_TOTAL_RSS = 11;
     private static final int INDEX_TOTAL_NATIVE_PSS = 12;
-    private static final int INDEX_LAST = 13;
+    private static final int INDEX_TOTAL_MEMTRACK_GRAPHICS = 13;
+    private static final int INDEX_TOTAL_MEMTRACK_GL = 14;
+    private static final int INDEX_LAST = 15;
 
     final class UiHandler extends Handler {
         public UiHandler() {
@@ -5624,6 +5627,23 @@
                 ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
+    @Override
+    public int[] checkUriPermissions(@NonNull List<Uri> uris, int pid, int uid,
+            final int modeFlags, IBinder callerToken) {
+        final int size = uris.size();
+        int[] res = new int[size];
+        // Default value DENIED.
+        Arrays.fill(res, PackageManager.PERMISSION_DENIED);
+
+        for (int i = 0; i < size; i++) {
+            final Uri uri = uris.get(i);
+            final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
+            res[i] = checkUriPermission(ContentProvider.getUriWithoutUserId(uri), pid, uid,
+                    modeFlags, userId, callerToken);
+        }
+        return res;
+    }
+
     /**
      * @param uri This uri must NOT contain an embedded userId.
      * @param userId The userId in which the uri is to be resolved.
@@ -10316,6 +10336,7 @@
         long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
         long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
         long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+        long[] memtrackTmp = new long[4];
 
         long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
         long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length];
@@ -10349,6 +10370,8 @@
                 final int reportType;
                 final long startTime;
                 final long endTime;
+                long memtrackGraphics = 0;
+                long memtrackGl = 0;
                 if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                     reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
                     startTime = SystemClock.currentThreadTimeMillis();
@@ -10360,7 +10383,7 @@
                 } else {
                     reportType = ProcessStats.ADD_PSS_EXTERNAL;
                     startTime = SystemClock.currentThreadTimeMillis();
-                    long pss = Debug.getPss(pid, tmpLong, null);
+                    long pss = Debug.getPss(pid, tmpLong, memtrackTmp);
                     if (pss == 0) {
                         continue;
                     }
@@ -10368,6 +10391,8 @@
                     endTime = SystemClock.currentThreadTimeMillis();
                     mi.dalvikPrivateDirty = (int) tmpLong[0];
                     mi.dalvikRss = (int) tmpLong[2];
+                    memtrackGraphics = memtrackTmp[1];
+                    memtrackGl = memtrackTmp[2];
                 }
                 if (!opts.isCheckinRequest && opts.dumpDetails) {
                     pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
@@ -10431,6 +10456,8 @@
                     ss[INDEX_TOTAL_PSS] += myTotalPss;
                     ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
                     ss[INDEX_TOTAL_RSS] += myTotalRss;
+                    ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+                    ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                             (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
                             myTotalSwapPss, myTotalRss, pid, hasActivities);
@@ -10494,6 +10521,8 @@
             final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1];
             mAppProfiler.forAllCpuStats((st) -> {
                 if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+                    long memtrackGraphics = 0;
+                    long memtrackGl = 0;
                     if (memInfos[0] == null) {
                         memInfos[0] = new Debug.MemoryInfo();
                     }
@@ -10503,13 +10532,15 @@
                             return;
                         }
                     } else {
-                        long pss = Debug.getPss(st.pid, tmpLong, null);
+                        long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp);
                         if (pss == 0) {
                             return;
                         }
                         info.nativePss = (int) pss;
                         info.nativePrivateDirty = (int) tmpLong[0];
                         info.nativeRss = (int) tmpLong[2];
+                        memtrackGraphics = memtrackTmp[1];
+                        memtrackGl = memtrackTmp[2];
                     }
 
                     final long myTotalPss = info.getTotalPss();
@@ -10519,6 +10550,8 @@
                     ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss;
                     ss[INDEX_TOTAL_RSS] += myTotalRss;
                     ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss;
+                    ss[INDEX_TOTAL_MEMTRACK_GRAPHICS] += memtrackGraphics;
+                    ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
@@ -10700,13 +10733,13 @@
             long kernelUsed = memInfo.getKernelUsedSizeKb();
             final long ionHeap = Debug.getIonHeapsSizeKb();
             final long ionPool = Debug.getIonPoolsSizeKb();
+            final long dmabufMapped = Debug.getDmabufMappedSizeKb();
             if (ionHeap >= 0 && ionPool >= 0) {
-                final long ionMapped = Debug.getIonMappedSizeKb();
-                final long ionUnmapped = ionHeap - ionMapped;
+                final long ionUnmapped = ionHeap - dmabufMapped;
                 pw.print("      ION: ");
                         pw.print(stringifyKBSize(ionHeap + ionPool));
                         pw.print(" (");
-                        pw.print(stringifyKBSize(ionMapped));
+                        pw.print(stringifyKBSize(dmabufMapped));
                         pw.print(" mapped + ");
                         pw.print(stringifyKBSize(ionUnmapped));
                         pw.print(" unmapped + ");
@@ -10715,11 +10748,61 @@
                 // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
                 // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
                 kernelUsed += ionHeap;
+            } else {
+                final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+                if (totalExportedDmabuf >= 0) {
+                    final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
+                    pw.print("DMA-BUF: ");
+                    pw.print(stringifyKBSize(totalExportedDmabuf));
+                    pw.print(" (");
+                    pw.print(stringifyKBSize(dmabufMapped));
+                    pw.print(" mapped + ");
+                    pw.print(stringifyKBSize(dmabufUnmapped));
+                    pw.println(" unmapped)");
+                    // Account unmapped dmabufs as part of kernel memory allocations
+                    kernelUsed += dmabufUnmapped;
+                    // Replace memtrack HAL reported Graphics category with mapped dmabufs
+                    ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GRAPHICS];
+                    ss[INDEX_TOTAL_PSS] += dmabufMapped;
+                }
+
+                // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not
+                // need to be added to kernelUsed.
+                final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb();
+                if (totalDmabufHeapExported >= 0) {
+                    pw.print("DMA-BUF Heaps: ");
+                    pw.println(stringifyKBSize(totalDmabufHeapExported));
+                }
+
+                final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+                if (totalDmabufHeapPool >= 0) {
+                    pw.print("DMA-BUF Heaps pool: ");
+                    pw.println(stringifyKBSize(totalDmabufHeapPool));
+                }
             }
             final long gpuUsage = Debug.getGpuTotalUsageKb();
             if (gpuUsage >= 0) {
-                pw.print("      GPU: "); pw.println(stringifyKBSize(gpuUsage));
+                final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+                if (gpuDmaBufUsage >= 0) {
+                    final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+                    pw.print("      GPU: ");
+                    pw.print(stringifyKBSize(gpuUsage));
+                    pw.print(" (");
+                    pw.print(stringifyKBSize(gpuDmaBufUsage));
+                    pw.print(" dmabuf + ");
+                    pw.print(stringifyKBSize(gpuPrivateUsage));
+                    pw.println(" private)");
+                    // Replace memtrack HAL reported GL category with private GPU allocations and
+                    // account it as part of kernel memory allocations
+                    ss[INDEX_TOTAL_PSS] -= ss[INDEX_TOTAL_MEMTRACK_GL];
+                    kernelUsed += gpuPrivateUsage;
+                } else {
+                    pw.print("      GPU: "); pw.println(stringifyKBSize(gpuUsage));
+                }
             }
+
+             // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+             // memInfo.getCachedSizeKb().
             final long lostRAM = memInfo.getTotalSizeKb()
                     - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 8f11a5a..c8630fa 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1322,7 +1322,7 @@
             infoMap.put(mi.pid, mi);
         }
         updateCpuStatsNow();
-        long[] memtrackTmp = new long[1];
+        long[] memtrackTmp = new long[4];
         long[] swaptrackTmp = new long[2];
         // Get a list of Stats that have vsize > 0
         final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0);
@@ -1345,6 +1345,8 @@
         long totalPss = 0;
         long totalSwapPss = 0;
         long totalMemtrack = 0;
+        long totalMemtrackGraphics = 0;
+        long totalMemtrackGl = 0;
         for (int i = 0, size = memInfos.size(); i < size; i++) {
             ProcessMemInfo mi = memInfos.get(i);
             if (mi.pss == 0) {
@@ -1355,6 +1357,8 @@
             totalPss += mi.pss;
             totalSwapPss += mi.swapPss;
             totalMemtrack += mi.memtrack;
+            totalMemtrackGraphics += memtrackTmp[1];
+            totalMemtrackGl += memtrackTmp[2];
         }
         Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
             @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
@@ -1512,25 +1516,73 @@
         final long ionHeap = Debug.getIonHeapsSizeKb();
         final long ionPool = Debug.getIonPoolsSizeKb();
         if (ionHeap >= 0 && ionPool >= 0) {
-            final long ionMapped = Debug.getIonMappedSizeKb();
-            final long ionUnmapped = ionHeap - ionMapped;
             memInfoBuilder.append("       ION: ");
             memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
             memInfoBuilder.append("\n");
             // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
             // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
             kernelUsed += ionHeap;
+        } else {
+            final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();
+            if (totalExportedDmabuf >= 0) {
+                final long dmabufMapped = Debug.getDmabufMappedSizeKb();
+                final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;
+                memInfoBuilder.append("DMA-BUF: ");
+                memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf));
+                memInfoBuilder.append("\n");
+                // Account unmapped dmabufs as part of kernel memory allocations
+                kernelUsed += dmabufUnmapped;
+                // Replace memtrack HAL reported Graphics category with mapped dmabufs
+                totalPss -= totalMemtrackGraphics;
+                totalPss += dmabufMapped;
+            }
+            // These are included in the totalExportedDmabuf above and hence do not need to be added
+            // to kernelUsed.
+            final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb();
+            if (totalExportedDmabufHeap >= 0) {
+                memInfoBuilder.append("DMA-BUF Heap: ");
+                memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap));
+                memInfoBuilder.append("\n");
+            }
+
+            final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb();
+            if (totalDmabufHeapPool >= 0) {
+                memInfoBuilder.append("DMA-BUF Heaps pool: ");
+                memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool));
+                memInfoBuilder.append("\n");
+            }
         }
+
         final long gpuUsage = Debug.getGpuTotalUsageKb();
         if (gpuUsage >= 0) {
-            memInfoBuilder.append("       GPU: ");
-            memInfoBuilder.append(stringifyKBSize(gpuUsage));
-            memInfoBuilder.append("\n");
+            final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb();
+            if (gpuDmaBufUsage >= 0) {
+                final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage;
+                memInfoBuilder.append("      GPU: ");
+                memInfoBuilder.append(stringifyKBSize(gpuUsage));
+                memInfoBuilder.append(" (");
+                memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage));
+                memInfoBuilder.append(" dmabuf + ");
+                memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage));
+                memInfoBuilder.append(" private)\n");
+                // Replace memtrack HAL reported GL category with private GPU allocations and
+                // account it as part of kernel memory allocations
+                totalPss -= totalMemtrackGl;
+                kernelUsed += gpuPrivateUsage;
+            } else {
+                memInfoBuilder.append("       GPU: ");
+                memInfoBuilder.append(stringifyKBSize(gpuUsage));
+                memInfoBuilder.append("\n");
+            }
+
         }
         memInfoBuilder.append("  Used RAM: ");
         memInfoBuilder.append(stringifyKBSize(
                                   totalPss - cachedPss + kernelUsed));
         memInfoBuilder.append("\n");
+
+        // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
+        // memInfo.getCachedSizeKb().
         memInfoBuilder.append("  Lost RAM: ");
         memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
                 - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index c6947c2d..52bb55f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.am;
 
-import static com.android.internal.power.MeasuredEnergyArray.SUBSYSTEM_DISPLAY;
-
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
@@ -39,13 +37,12 @@
 import android.telephony.TelephonyManager;
 import android.util.IntArray;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -148,13 +145,13 @@
     private WifiActivityEnergyInfo mLastWifiInfo =
             new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
 
-    /** Maps the EnergyConsumer id to it's corresponding {@link MeasuredEnergySubsystem} */
+    /**
+     * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
+     * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
+     */
+    // TODO(b/180029015): Hook this up (it isn't used yet)
     @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mEnergyConsumerToSubsystemMap = null;
-
-    /** Maps a {@link MeasuredEnergySubsystem} to it's corresponding EnergyConsumer id */
-    @GuardedBy("mWorkerLock")
-    private @Nullable SparseIntArray mSubsystemToEnergyConsumerMap = null;
+    private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
     /** Snapshot of measured energies, or null if no measured energies are supported. */
     @GuardedBy("mWorkerLock")
@@ -204,18 +201,26 @@
             mWifiManager = wm;
             mTelephony = tm;
             mPowerStatsInternal = psi;
+
+            boolean[] supportedStdBuckets = null;
+            int numCustomBuckets = 0;
             if (mPowerStatsInternal != null) {
-                populateEnergyConsumerSubsystemMapsLocked();
-                final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
-                mMeasuredEnergySnapshot = initialMeasuredEnergies == null
-                        ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
-                final boolean[] supportedStdBuckets
-                        = getSupportedEnergyBuckets(initialMeasuredEnergies);
-                final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
-                synchronized (mStats) {
-                    mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+                final SparseArray<EnergyConsumer> idToConsumer
+                        = populateEnergyConsumerSubsystemMapsLocked();
+                if (idToConsumer != null) {
+                    mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+                    final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData();
+                    // According to spec, initialEcrs will include 0s for consumers that haven't
+                    // used any energy yet, as long as they are supported; however, attributed uid
+                    // energies will be absent if their energy is 0.
+                    mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs);
+                    numCustomBuckets = mMeasuredEnergySnapshot.getNumOtherOrdinals();
+                    supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
             }
+            synchronized (mStats) {
+                mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
+            }
         }
     }
 
@@ -568,7 +573,9 @@
         } catch (ExecutionException e) {
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
-        final SparseLongArray energyDeltas = mMeasuredEnergySnapshot == null ? null :
+
+        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas =
+                mMeasuredEnergySnapshot == null ? null :
                 mMeasuredEnergySnapshot.updateAndGetDelta(getMeasuredEnergyLocked(updateFlags));
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -576,6 +583,7 @@
         final long elapsedRealtimeUs = elapsedRealtime * 1000;
         final long uptimeUs = uptime * 1000;
 
+        // Now that we have finally received all the data, we can tell mStats about it.
         synchronized (mStats) {
             mStats.addHistoryEventLocked(
                     elapsedRealtime,
@@ -601,10 +609,21 @@
             }
 
             // Inform mStats about each applicable measured energy.
-            if (energyDeltas != null) {
-                final long displayEnergy = energyDeltas.get(SUBSYSTEM_DISPLAY, 0L);
-                // Always pass in what BatteryExternalStatsWorker thinks screenState is.
-                mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+            if (measuredEnergyDeltas != null) {
+                final long displayEnergy = measuredEnergyDeltas.displayEnergyUJ;
+                if (displayEnergy != MeasuredEnergySnapshot.UNAVAILABLE) {
+                    // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
+                    mStats.updateDisplayEnergyLocked(displayEnergy, screenState, elapsedRealtime);
+                }
+            }
+            // Inform mStats about each applicable custom energy bucket.
+            if (measuredEnergyDeltas != null && measuredEnergyDeltas.otherTotalEnergyUJ != null) {
+                // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
+                for (int ord = 0; ord < measuredEnergyDeltas.otherTotalEnergyUJ.length; ord++) {
+                    long totalEnergy = measuredEnergyDeltas.otherTotalEnergyUJ[ord];
+                    SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidEnergiesUJ[ord];
+                    mStats.updateCustomMeasuredEnergyDataLocked(ord, totalEnergy, uidEnergies);
+                }
             }
 
             if (bluetoothInfo != null) {
@@ -621,7 +640,8 @@
 
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
-                // TODO: wifiEnergyDelta = energyDeltas.get(MeasuredEnergyArray.SUBSYSTEM_WIFI, 0L);
+                // TODO: wifiEnergyDelta = measuredEnergyDeltas.consumerTypeEnergyUJ
+                //               .get(EnergyConsumerType.WIFI, MeasuredEnergySnapshot.UNAVAILABLE)
                 mStats.updateWifiState(extractDeltaLocked(wifiInfo)
                         /*, TODO: wifiEnergyDelta */, elapsedRealtime, uptime);
             } else {
@@ -740,21 +760,23 @@
     }
 
     /**
-     * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
+     * Map the {@link EnergyConsumerType}s in the given energyArray to
      * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
      * Does not include custom energy buckets (which are always, by definition, supported).
      *
      * @return array with true for index i if standard energy bucket i is supported.
      */
-    private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    private static @Nullable boolean[] getSupportedEnergyBuckets(
+            SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) {
             return null;
         }
         final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
-        final int size = energyArray.size();
-        for (int energyIdx = 0; energyIdx < size; energyIdx++) {
-            switch (energyArray.getSubsystem(energyIdx)) {
-                case MeasuredEnergyArray.SUBSYSTEM_DISPLAY:
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            switch (consumer.type) {
+                case EnergyConsumerType.DISPLAY:
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE] = true;
                     buckets[MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER] = true;
@@ -764,71 +786,22 @@
         return buckets;
     }
 
-    /**
-     * Get a {@link MeasuredEnergyArray} with the latest
-     * {@link MeasuredEnergyArray.MeasuredEnergySubsystem} energy usage since boot.
-     *
-     * TODO(b/176988041): Replace {@link MeasuredEnergyArray} usage with {@link
-     * EnergyConsumerResult}[]
-     */
+    /** Get {@link EnergyConsumerResult}s with the latest energy usage since boot. */
     @GuardedBy("mWorkerLock")
-    @VisibleForTesting
-    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
-        final EnergyConsumerResult[] results;
+    private @Nullable EnergyConsumerResult[] getEnergyConsumptionData() {
         try {
-            results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
+            return mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
                     .get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getEnergyConsumedAsync", e);
             return null;
         }
-        if (results == null) return null;
-        final int size = results.length;
-        final int[] subsystems = new int[size];
-        final long[] energyUJ = new long[size];
-
-        int count = 0;
-        for (int i = 0; i < size; i++) {
-            final EnergyConsumerResult consumer = results[i];
-            final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
-                    MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
-            if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[count] = subsystem;
-            energyUJ[count] = consumer.energyUWs;
-            count++;
-        }
-        final int arraySize = count;
-        return new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return subsystems[index];
-            }
-
-            @Override
-            public long getEnergy(int index) {
-                if (index >= size()) {
-                    throw new IllegalArgumentException(
-                            "Out of bounds subsystem index! index : " + index + ", size : "
-                                    + size());
-                }
-                return energyUJ[index];
-            }
-
-            @Override
-            public int size() {
-                return arraySize;
-            }
-        };
     }
 
-    /** Fetch MeasuredEnergyArray for supported subsystems based on the given updateFlags. */
+    /** Fetch EnergyConsumerResult[] for supported subsystems based on the given updateFlags. */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getMeasuredEnergyLocked(@ExternalUpdateFlag int flags) {
+    private @Nullable EnergyConsumerResult[] getMeasuredEnergyLocked(@ExternalUpdateFlag int flags)
+    {
         if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
@@ -836,72 +809,62 @@
             return getEnergyConsumptionData();
         }
 
-        final List<Integer> energySubsystems = new ArrayList<>();
+        final List<Integer> energyConsumerIds = new ArrayList<>();
         if ((flags & UPDATE_DISPLAY) != 0) {
-            addEnergyConsumerIdLocked(energySubsystems, SUBSYSTEM_DISPLAY);
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.DISPLAY);
         }
         // TODO: Wifi, Bluetooth, etc., go here
-        if (energySubsystems.isEmpty()) {
+
+        if (energyConsumerIds.isEmpty()) {
             return null;
         }
-        // TODO: Query *specific* subsystems from HAL based on energyConsumerIds.toArray()
+        // TODO(b/180029015): Query specific subsystems from HAL based on energyConsumerIds.toArray
         return getEnergyConsumptionData();
     }
 
     @GuardedBy("mWorkerLock")
-    private void addEnergyConsumerIdLocked(List<Integer> energyConsumerIds,
-            @MeasuredEnergyArray.MeasuredEnergySubsystem int consumerId) {
-        if (mMeasuredEnergySnapshot.hasSubsystem(consumerId)) {
-            energyConsumerIds.add(consumerId);
-        }
+    private void addEnergyConsumerIdLocked(
+            List<Integer> energyConsumerIds, @EnergyConsumerType int type) {
+        final int consumerId = 0; // TODO(b/180029015): Use mEnergyConsumerTypeToIdMap to get this
+        energyConsumerIds.add(consumerId);
     }
 
+    /** Populates the cached type->ids map, and returns the (inverse) id->EnergyConsumer map. */
     @GuardedBy("mWorkerLock")
-    private void populateEnergyConsumerSubsystemMapsLocked() {
+    private @Nullable SparseArray<EnergyConsumer> populateEnergyConsumerSubsystemMapsLocked() {
         if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+            return null;
         }
         final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
-        if (energyConsumers == null) {
-            // EnergyConsumer data unavailable, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
+        if (energyConsumers == null || energyConsumers.length == 0) {
+            return null;
         }
 
-        final int length = energyConsumers.length;
-        if (length == 0) {
-            // EnergyConsumer array empty, don't bother populating maps.
-            mEnergyConsumerToSubsystemMap = null;
-            mSubsystemToEnergyConsumerMap = null;
-            return;
-        } else {
-            mEnergyConsumerToSubsystemMap = new SparseIntArray(length);
-            mSubsystemToEnergyConsumerMap = new SparseIntArray(length);
-        }
+        // TODO(b/180029015): Initialize typeToIds
+        // Maps type -> {ids} (1:n map, since multiple ids might have the same type)
+        // final SparseArray<SparseIntArray> typeToIds = new SparseArray<>();
+
+        // Maps id -> EnergyConsumer (1:1 map)
+        final SparseArray<EnergyConsumer> idToConsumer = new SparseArray<>(energyConsumers.length);
 
         // Add all expected EnergyConsumers to the maps
-        for (int i = 0; i < length; i++) {
-            final EnergyConsumer consumer = energyConsumers[i];
-            switch (consumer.type) {
-                case EnergyConsumerType.DISPLAY:
-                    if (consumer.ordinal == 0) {
-                        mEnergyConsumerToSubsystemMap.put(consumer.id,
-                                MeasuredEnergyArray.SUBSYSTEM_DISPLAY);
-                        mSubsystemToEnergyConsumerMap.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY,
-                                consumer.id);
-                    } else {
-                        Slog.w(TAG, "Unexpected ordinal (" + consumer.ordinal
-                                + ") for EnergyConsumerType.DISPLAY");
-                    }
-                    break;
-                default:
-                    Slog.w(TAG, "Unexpected EnergyConsumerType (" + consumer.type + ")");
+        for (final EnergyConsumer consumer : energyConsumers) {
+            // Check for inappropriate ordinals
+            if (consumer.ordinal != 0) {
+                switch (consumer.type) {
+                    case EnergyConsumerType.OTHER:
+                    case EnergyConsumerType.CPU_CLUSTER:
+                        break;
+                    default:
+                        Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
+                                + consumer.ordinal + " for type " + consumer.type);
+                        continue; // Ignore this consumer
+                }
             }
-
+            idToConsumer.put(consumer.id, consumer);
+            // TODO(b/180029015): Also populate typeToIds map
         }
+        // TODO(b/180029015): Store typeToIds in mEnergyConsumerTypeToIdMap.
+        return idToConsumer;
     }
 }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 773e313..6500f6d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -72,6 +72,7 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ParseUtils;
@@ -126,15 +127,20 @@
                     .onMalformedInput(CodingErrorAction.REPLACE)
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
                     .replaceWith("?");
-    private static final int MAX_LOW_POWER_STATS_SIZE = 4096;
+    private static final int MAX_LOW_POWER_STATS_SIZE = 8192;
     private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
+    private static final String EMPTY = "Empty";
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Object mLock = new Object();
 
+    private final Object mPowerStatsLock = new Object();
+    @GuardedBy("mPowerStatsLock")
     private PowerStatsInternal mPowerStatsInternal = null;
+    @GuardedBy("mPowerStatsLock")
     private Map<Integer, String> mEntityNames = new HashMap();
+    @GuardedBy("mPowerStatsLock")
     private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
 
     @GuardedBy("mStats")
@@ -172,13 +178,6 @@
             };
 
     private void populatePowerEntityMaps() {
-        if (mPowerStatsInternal == null) {
-            // PowerStatsInternal unavailable, don't bother populating maps.
-            mEntityNames = null;
-            mStateNames = null;
-            return;
-        }
-
         PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
         if (entities == null) {
             return;
@@ -202,6 +201,12 @@
      */
     @Override
     public void fillLowPowerStats(RpmStats rpmStats) {
+        synchronized (mPowerStatsLock) {
+            if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+                return;
+            }
+        }
+
         final StateResidencyResult[] results;
         try {
             results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
@@ -237,16 +242,22 @@
 
     @Override
     public String getSubsystemLowPowerStats() {
+        synchronized (mPowerStatsLock) {
+            if (mPowerStatsInternal == null || mEntityNames.isEmpty() || mStateNames.isEmpty()) {
+                return EMPTY;
+            }
+        }
+
         final StateResidencyResult[] results;
         try {
             results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
                     .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (Exception e) {
             Slog.e(TAG, "Failed to getStateResidencyAsync", e);
-            return "Empty";
+            return EMPTY;
         }
 
-        if (results.length == 0) return "Empty";
+        if (results.length == 0) return EMPTY;
 
         int charsLeft = MAX_LOW_POWER_STATS_SIZE;
         StringBuilder builder = new StringBuilder("SubsystemPowerState");
@@ -322,9 +333,14 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
         }
-        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
-        if (mPowerStatsInternal != null) {
-            populatePowerEntityMaps();
+
+        synchronized (mPowerStatsLock) {
+            mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+            if (mPowerStatsInternal != null) {
+                populatePowerEntityMaps();
+            } else {
+                Slog.e(TAG, "Could not register PowerStatsInternal");
+            }
         }
 
         Watchdog.getInstance().addMonitor(this);
@@ -342,6 +358,11 @@
         }
 
         @Override
+        public SystemServiceCpuThreadTimes getSystemServiceCpuThreadTimes() {
+            return mStats.getSystemServiceCpuThreadTimes();
+        }
+
+        @Override
         public void noteJobsDeferred(int uid, int numDeferred, long sinceLast) {
             if (DBG) Slog.d(TAG, "Jobs deferred " + uid + ": " + numDeferred + " " + sinceLast);
             BatteryStatsService.this.noteJobsDeferred(uid, numDeferred, sinceLast);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7e65434..8b6fabd 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -967,6 +967,12 @@
         }
     }
 
+    static String broadcastDescription(BroadcastRecord r, ComponentName component) {
+        return r.intent.toString()
+                + " from " + r.callerPackage + " (pid=" + r.callingPid
+                + ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
+    }
+
     final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
         BroadcastRecord r;
 
@@ -1349,14 +1355,18 @@
                         < brOptions.getMinManifestReceiverApiLevel() ||
                 info.activityInfo.applicationInfo.targetSdkVersion
                         > brOptions.getMaxManifestReceiverApiLevel())) {
+            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+                    + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
+                    + " but delivery restricted to ["
+                    + brOptions.getMinManifestReceiverApiLevel() + ", "
+                    + brOptions.getMaxManifestReceiverApiLevel()
+                    + "] broadcasting " + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
             Slog.w(TAG, "Association not allowed: broadcasting "
-                    + r.intent.toString()
-                    + " from " + r.callerPackage + " (pid=" + r.callingPid
-                    + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                    + broadcastDescription(r, component));
             skip = true;
         }
         if (!skip) {
@@ -1364,9 +1374,7 @@
                     r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
             if (skip) {
                 Slog.w(TAG, "Firewall blocked: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ") to " + component.flattenToShortString());
+                        + broadcastDescription(r, component));
             }
         }
         int perm = mService.checkComponentPermission(info.activityInfo.permission,
@@ -1375,18 +1383,12 @@
         if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
             if (!info.activityInfo.exported) {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
             } else {
                 Slog.w(TAG, "Permission Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid=" + r.callingPid
-                        + ", uid=" + r.callingUid + ")"
-                        + " requires " + info.activityInfo.permission
-                        + " due to receiver " + component.flattenToShortString());
+                        + broadcastDescription(r, component)
+                        + " requires " + info.activityInfo.permission);
             }
             skip = true;
         } else if (!skip && info.activityInfo.permission != null) {
@@ -1396,13 +1398,9 @@
                     "Broadcast delivered to " + info.activityInfo.name)
                     != AppOpsManager.MODE_ALLOWED) {
                 Slog.w(TAG, "Appop Denial: broadcasting "
-                        + r.intent.toString()
-                        + " from " + r.callerPackage + " (pid="
-                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + broadcastDescription(r, component)
                         + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission)
-                        + " due to registered receiver "
-                        + component.flattenToShortString());
+                                info.activityInfo.permission));
                 skip = true;
             }
         }
@@ -1520,7 +1518,7 @@
                         + info.activityInfo.packageName, e);
             }
             if (!isAvailable) {
-                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+                Slog.w(TAG_BROADCAST,
                         "Skipping delivery to " + info.activityInfo.packageName + " / "
                         + info.activityInfo.applicationInfo.uid
                         + " : package no longer available");
@@ -1536,6 +1534,9 @@
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                     info.activityInfo.packageName, UserHandle.getUserId(
                             info.activityInfo.applicationInfo.uid))) {
+                Slog.w(TAG_BROADCAST,
+                        "Skipping delivery: permission review required for "
+                                + broadcastDescription(r, component));
                 skip = true;
             }
         }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 0afdbde..7bdf43c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -538,6 +538,12 @@
     private static native int getBinderFreezeInfo(int pid);
 
     /**
+     * Returns the path to be checked to verify whether the freezer is supported by this system.
+     * @return absolute path to the file
+     */
+    private static native String getFreezerCheckPath();
+
+    /**
      * Determines whether the freezer is supported by this system
      */
     public static boolean isFreezerSupported() {
@@ -545,11 +551,15 @@
         FileReader fr = null;
 
         try {
-            fr = new FileReader("/sys/fs/cgroup/uid_0/cgroup.freeze");
+            fr = new FileReader(getFreezerCheckPath());
             char state = (char) fr.read();
 
             if (state == '1' || state == '0') {
                 supported = true;
+                // This is a workaround after reverting the cgroup v2 uid/pid hierarchy due to
+                // http://b/179006802.
+                // TODO: remove once the uid/pid hierarchy is restored
+                enableFreezerInternal(true);
             } else {
                 Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
             }
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index b915c0c..9e0aa32 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -17,132 +17,260 @@
 package com.android.server.am;
 
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyArray;
-import com.android.internal.power.MeasuredEnergyArray.MeasuredEnergySubsystem;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 
 /**
- * Keeps snapshots of data from previously pulled MeasuredEnergyArrays.
+ * Keeps snapshots of data from previously pulled EnergyConsumerResults.
  */
 @VisibleForTesting
 public class MeasuredEnergySnapshot {
     private static final String TAG = "MeasuredEnergySnapshot";
 
-    private static final long UNAVAILABLE = -1;
+    public static final long UNAVAILABLE = -1L;
+
+    /** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
+    private final SparseArray<EnergyConsumer> mEnergyConsumers;
+
+    /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private final int mNumOtherOrdinals;
 
     /**
-     * Energy snapshots from the last time each {@link MeasuredEnergySubsystem} was updated.
+     * Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
+     * each {@link EnergyConsumer} was updated.
      *
-     * Note that the snapshots for different subsystems may have been taken at different times.
+     * Note that the snapshots for different ids may have been taken at different times.
+     * Note that energies for all existing ids are stored here, including each ordinal of type
+     * {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
      *
-     * A snapshot is {@link #UNAVAILABLE} if the subsystem has never been updated (ie. unsupported).
+     * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
      */
-    private final long[] mMeasuredEnergySnapshots;
+    private final SparseLongArray mMeasuredEnergySnapshots;
 
     /**
-     * Constructor that initializes to the given energyArray;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
+     * {@link EnergyConsumerType#OTHER} was updated.
+     * It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
+     * uid to an energy (UJ). That is,
+     * mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
+     *
+     * If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
+     * If an id is present but a uid is not present, that uid's energy is 0.
      */
-    public MeasuredEnergySnapshot(MeasuredEnergyArray initialEnergyArray) {
-        this(MeasuredEnergyArray.NUMBER_SUBSYSTEMS, initialEnergyArray);
+    private final SparseArray<SparseLongArray> mAttributionSnapshots;
+
+    /**
+     * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
+     * exist and what their details are.
+     */
+    MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+        mEnergyConsumers = idToConsumerMap;
+        mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+
+        mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
+        mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
 
     /**
-     * Constructor (for testing) that initializes to the given energyArray and numSubsystems;
-     * all subsystems not mentioned in initialEnergyArray are set to UNAVAILABLE.
+     * Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
+     * custom energy buckets supported by the device.
      */
-    @VisibleForTesting
-    MeasuredEnergySnapshot(int numSubsystems, MeasuredEnergyArray initialEnergyArray) {
-        if (initialEnergyArray.size() > numSubsystems) {
-            throw new IllegalArgumentException("Energy array contains " + initialEnergyArray.size()
-                    + " subsystems, which exceeds the maximum allowed of " + numSubsystems);
-        }
-        mMeasuredEnergySnapshots = new long[numSubsystems];
-        Arrays.fill(mMeasuredEnergySnapshots, UNAVAILABLE);
-        fillGivenSubsystems(initialEnergyArray);
+    public int getNumOtherOrdinals() {
+        return mNumOtherOrdinals;
     }
 
-    /**
-     * For the subsystems present in energyArray, overwrites mMeasuredEnergySnapshots with their
-     * energy values from energyArray.
-     */
-    private void fillGivenSubsystems(MeasuredEnergyArray energyArray) {
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int subsystem = energyArray.getSubsystem(i);
-            mMeasuredEnergySnapshots[subsystem] = energyArray.getEnergy(i);
-        }
+    /** Class for returning measured energy delta data. */
+    static class MeasuredEnergyDeltaData {
+        /** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
+        public long displayEnergyUJ = UNAVAILABLE;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
+        public @Nullable long[] otherTotalEnergyUJ = null;
+
+        /** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
+        public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
     }
 
     /**
      * Update with the some freshly measured energies and return the difference (delta)
      * between the previously stored values and the passed-in values.
      *
-     * @param energyArray measured energy array for some (possibly not all) subsystems.
+     * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
+     *             Consumers that are not present are ignored (they are *not* treated as 0).
      *
-     * @return a map from the updated subsystems to their corresponding energy deltas.
-     *         Subsystems not present in energyArray will not appear.
-     *         Subsystems with no difference in energy will not appear.
-     *         Returns null, if energyArray is null.
+     * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+     *         their corresponding energy deltas.
+     *         Fields with no interesting data (consumers not present in ecrs or with no energy
+     *         difference) will generally be left as their default values.
+     *         otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
+     *         length {@link #getNumOtherOrdinals()}.
+     *         Returns null, if ecrs is null or empty.
      */
-    public @Nullable SparseLongArray updateAndGetDelta(MeasuredEnergyArray energyArray) {
-        if (energyArray == null) {
+    public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
+        if (ecrs == null || ecrs.length == 0) {
             return null;
         }
-        final SparseLongArray delta = new SparseLongArray();
-        final int size = energyArray.size();
-        for (int i = 0; i < size; i++) {
-            final int updatedSubsystem = energyArray.getSubsystem(i);
-            final long newEnergyUJ = energyArray.getEnergy(i);
-            final long oldEnergyUJ = mMeasuredEnergySnapshots[updatedSubsystem];
+        final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
 
-            // If this is the first valid energy, there is no delta to take.
-            if (oldEnergyUJ < 0) continue;
-            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
-            if (deltaUJ == 0) continue;
-            if (deltaUJ < 0) {
-                Slog.e(TAG, "For subsystem " + updatedSubsystem + ", new energy (" + newEnergyUJ
-                        + ") is less than old energy (" + oldEnergyUJ + "). Skipping. ");
+        for (final EnergyConsumerResult ecr : ecrs) {
+            // Extract the new energy data for the current consumer.
+            final int consumerId = ecr.id;
+            final long newEnergyUJ = ecr.energyUWs;
+            final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
+
+            // Look up the static information about this consumer.
+            final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
+            if (consumer == null) {
+                Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
                 continue;
             }
-            delta.put(updatedSubsystem, deltaUJ);
+            final int type = consumer.type;
+            final int ordinal = consumer.ordinal;
+
+            // Look up, and update, the old energy information about this consumer.
+            final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+            mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+            final SparseLongArray otherUidEnergies
+                    = updateAndGetDeltaForTypeOther(consumer, newAttributions);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+
+            // NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
+            // there's no attribution either. Technically that isn't enforced at the HAL, but we
+            // can't really trust data like that anyway.
+
+            if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+                        + ") < old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+
+            switch (type) {
+                case EnergyConsumerType.DISPLAY:
+                    output.displayEnergyUJ = deltaUJ;
+                    break;
+                case EnergyConsumerType.OTHER:
+                    if (output.otherTotalEnergyUJ == null) {
+                        output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
+                        output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
+                    }
+                    output.otherTotalEnergyUJ[ordinal] = deltaUJ;
+                    output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
+                    break;
+                default:
+                    Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
+
+            }
         }
-
-        fillGivenSubsystems(energyArray);
-
-        return delta;
+        return output;
     }
 
     /**
-     * Check if a subsystem's measured energy is available.
-     * @param subsystem which subsystem.
-     * @return true if subsystem is available.
+     * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
+     * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
+     * difference (delta) between the previously stored values and the passed-in values.
+     *
+     * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
+     * @param newAttributions Record of uids and their new energyUJ values.
+     *                        Any uid not present is treated as having energy 0.
+     *                        If null or empty, all uids are treated as having energy 0.
+     * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
+     *         consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
+     *         Returns null if no delta available to calculate.
      */
-    public boolean hasSubsystem(@MeasuredEnergySubsystem int subsystem) {
-        return mMeasuredEnergySnapshots[subsystem] != UNAVAILABLE;
+    private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
+            @NonNull EnergyConsumer consumerInfo,
+            @Nullable EnergyConsumerAttribution[] newAttributions) {
+
+        if (consumerInfo.type != EnergyConsumerType.OTHER) {
+            return null;
+        }
+        if (newAttributions == null) {
+            // Treat null as empty (i.e. all uids have 0 energy).
+            newAttributions = new EnergyConsumerAttribution[0];
+        }
+
+        // SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
+        SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
+
+        // If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
+        if (uidOldEnergyMap == null) {
+            uidOldEnergyMap = new SparseLongArray(newAttributions.length);
+            mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
+            for (EnergyConsumerAttribution newAttribution : newAttributions) {
+                uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
+            }
+            return null;
+        }
+
+        // Map uid -> energyDelta. No initial capacity since many deltas might be 0.
+        final SparseLongArray uidEnergyDeltas = new SparseLongArray();
+
+        for (EnergyConsumerAttribution newAttribution : newAttributions) {
+            final int uid = newAttribution.uid;
+            final long newEnergyUJ = newAttribution.energyUWs;
+            // uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
+            final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
+            uidOldEnergyMap.put(uid, newEnergyUJ);
+
+            // Everything is fully done being updated. We now calculate the delta for returning.
+            if (oldEnergyUJ < 0) continue;
+            if (newEnergyUJ == oldEnergyUJ) continue;
+            final long deltaUJ = newEnergyUJ - oldEnergyUJ;
+            if (deltaUJ < 0) {
+                Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+                        + ") but old energy (" + oldEnergyUJ + "). Skipping. ");
+                continue;
+            }
+            uidEnergyDeltas.put(uid, deltaUJ);
+        }
+        return uidEnergyDeltas;
     }
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Measured energy snapshot (microjoules):");
-        pw.print("   ");
-        for (int i = 0; i < MeasuredEnergyArray.NUMBER_SUBSYSTEMS; i++) {
-            final long energyUJ = mMeasuredEnergySnapshots[i];
-            if (energyUJ == UNAVAILABLE) continue;
-            pw.print(MeasuredEnergyArray.SUBSYSTEM_NAMES[i]);
-            pw.print(" : ");
-            pw.print(energyUJ);
-            if (i != MeasuredEnergyArray.NUMBER_SUBSYSTEMS - 1) {
-                pw.print(", ");
-            }
+        pw.println("Measured energy snapshot");
+        pw.println("List of EnergyConsumers:");
+        for (int i = 0; i < mEnergyConsumers.size(); i++) {
+            final int id = mEnergyConsumers.keyAt(i);
+            final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
+            pw.println(String.format("    Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
+                    consumer.id, consumer.ordinal, consumer.type, consumer.name));
         }
+        pw.println("Map of consumerIds to energy (in microjoules):");
+        for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
+            final int id = mMeasuredEnergySnapshots.keyAt(i);
+            final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+            pw.println(String.format("    Consumer %d has energy %d uJ}", id, energyUJ));
+        }
+        pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
+        pw.println("    " + mAttributionSnapshots);
         pw.println();
     }
+
+    /** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
+    private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
+        if (idToConsumer == null) return 0;
+        int numOtherOrdinals = 0;
+        final int size = idToConsumer.size();
+        for (int idx = 0; idx < size; idx++) {
+            final EnergyConsumer consumer = idToConsumer.valueAt(idx);
+            if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
+        }
+        return numOtherOrdinals;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0576345..172f707 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2407,6 +2407,11 @@
                         app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
                         false, false,
                         new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
+
+                if (Process.createProcessGroup(uid, startResult.pid) < 0) {
+                    Slog.e(ActivityManagerService.TAG, "Unable to create process group for "
+                            + app.processName + " (" + startResult.pid + ")");
+                }
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index e533cc3..47573f3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -607,7 +607,8 @@
     @GuardedBy("mService")
     void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
         synchronized (mProfilerLock) {
-            pw.print(" lastPssTime=");
+            pw.print(prefix);
+            pw.print("lastPssTime=");
             TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
             pw.print(" pssProcState=");
             pw.print(mPssProcState);
@@ -629,9 +630,8 @@
             DebugUtils.printSizeValue(pw, mLastRss * 1024);
             pw.println();
             pw.print(prefix);
-            pw.print(" trimMemoryLevel=");
+            pw.print("trimMemoryLevel=");
             pw.println(mTrimMemoryLevel);
-            pw.println();
             pw.print(prefix); pw.print("procStateMemTracker: ");
             mProcStateMemTracker.dumpLine(pw);
             pw.print(prefix);
@@ -653,5 +653,6 @@
             pw.print(" timeUsed=");
             TimeUtils.formatDuration(mCurCpuTime.get() - lastCpuTime, pw);
         }
+        pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index da8aeb5..42e7ff4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -425,9 +425,10 @@
             pw.print(prefix); pw.print("mInstr="); pw.println(mInstr);
         }
         pw.print(prefix); pw.print("thread="); pw.println(mThread);
-        pw.print(prefix); pw.print("pid="); pw.print(mPid);
+        pw.print(prefix); pw.print("pid="); pw.println(mPid);
         pw.print(prefix); pw.print("lastActivityTime=");
         TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
+        pw.println();
         if (mPersistent || mRemoved) {
             pw.print(prefix); pw.print("persistent="); pw.print(mPersistent);
             pw.print(" removed="); pw.println(mRemoved);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index e1a153d..499fbcb 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1277,7 +1277,7 @@
         pw.print(" set="); pw.println(mSetAdj);
         pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
         pw.print(" setSchedGroup="); pw.print(mSetSchedGroup);
-        pw.print(" systemNoUi="); pw.print(mSystemNoUi);
+        pw.print(" systemNoUi="); pw.println(mSystemNoUi);
         pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
         pw.print(" mRepProcState="); pw.print(mRepProcState);
         pw.print(" setProcState="); pw.print(mSetProcState);
@@ -1297,7 +1297,7 @@
         }
         if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) {
             pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
-            pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean());
+            pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean());
         }
         pw.print(prefix); pw.print("cached="); pw.print(mCached);
         pw.print(" empty="); pw.println(mEmpty);
@@ -1316,12 +1316,12 @@
         }
         if (mHasForegroundActivities || mRepForegroundActivities) {
             pw.print(prefix);
-            pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
+            pw.print("foregroundActivities="); pw.print(mHasForegroundActivities);
             pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")");
         }
         if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) {
             pw.print(prefix);
-            pw.print(" whenUnimportant=");
+            pw.print("whenUnimportant=");
             TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
             pw.println();
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 10fe1e1..c92abd4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3131,11 +3131,6 @@
                 return AppOpsManager.MODE_ERRORED;
             }
             final Op op = getOpLocked(ops, code, uid, true);
-            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
-                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
-                        AppOpsManager.MODE_IGNORED);
-                return AppOpsManager.MODE_IGNORED;
-            }
             final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
             if (attributedOp.isRunning()) {
                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
@@ -3145,6 +3140,12 @@
 
             final int switchCode = AppOpsManager.opToSwitch(code);
             final UidState uidState = ops.uidState;
+            if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+                attributedOp.rejected(uidState.state, flags);
+                scheduleOpNotedIfNeededLocked(code, uid, packageName, flags,
+                        AppOpsManager.MODE_IGNORED);
+                return AppOpsManager.MODE_IGNORED;
+            }
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 68a084e..8af1b5be 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -264,8 +264,8 @@
      */
     public void playerEvent(int piid, int event, int deviceId, int binderUid) {
         if (DEBUG) {
-            Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%d)",
-                    piid, deviceId, event));
+            Log.v(TAG, String.format("playerEvent(piid=%d, deviceId=%d, event=%s)",
+                    piid, deviceId, AudioPlaybackConfiguration.playerStateToString(event)));
         }
         final boolean change;
         synchronized(mPlayerLock) {
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 14292d9c..f888200 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -257,11 +257,15 @@
                             mUserId,
                             mOpPackageName,
                             mOperationId);
+                    mState = STATE_AUTH_STARTED;
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Remote exception", e);
                 }
+            } else {
+                // The UI was already showing :)
+                mState = STATE_AUTH_STARTED_UI_SHOWING;
             }
-            mState = STATE_AUTH_STARTED;
+
         }
     }
 
@@ -686,7 +690,8 @@
      * @return true if this AuthSession is finished, e.g. should be set to null
      */
     boolean onCancelAuthSession(boolean force) {
-        final boolean authStarted = mState == STATE_AUTH_STARTED
+        final boolean authStarted = mState == STATE_AUTH_CALLED
+                || mState == STATE_AUTH_STARTED
                 || mState == STATE_AUTH_STARTED_UI_SHOWING;
 
         if (authStarted && !force) {
@@ -793,7 +798,7 @@
             case BiometricAuthenticator.TYPE_FINGERPRINT:
                 return FingerprintManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
             case BiometricAuthenticator.TYPE_FACE:
-                return FaceManager.getAcquiredString(mContext, acquiredInfo, vendorCode);
+                return FaceManager.getAuthHelpMessage(mContext, acquiredInfo, vendorCode);
             default:
                 return null;
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 0536e78..b31a54b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -311,4 +311,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_AUTHENTICATE;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 8fa3bbb..81ce2d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -82,12 +82,18 @@
     @NonNull protected Callback mCallback;
 
     /**
-     * Returns a ClientMonitorEnum constant defined in biometrics.proto
-     * @return
+     * @return A ClientMonitorEnum constant defined in biometrics.proto
      */
     public abstract int getProtoEnum();
 
     /**
+     * @return True if the ClientMonitor should cancel any current and pending interruptable clients
+     */
+    public boolean interruptsPrecedingClients() {
+        return false;
+    }
+
+    /**
      * @param context    system_server context
      * @param token      a unique token for the client
      * @param listener   recipient of related events (e.g. authentication)
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c5237ab..20c25c3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -475,14 +475,17 @@
      */
     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
             @Nullable BaseClientMonitor.Callback clientCallback) {
-        // Mark any interruptable pending clients as canceling. Once they reach the head of the
-        // queue, the scheduler will send ERROR_CANCELED and skip the operation.
-        for (Operation operation : mPendingOperations) {
-            if (operation.mClientMonitor instanceof Interruptable
-                    && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
-                Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
-                        + operation.mClientMonitor);
-                operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+        // If the incoming operation should interrupt preceding clients, mark any interruptable
+        // pending clients as canceling. Once they reach the head of the queue, the scheduler will
+        // send ERROR_CANCELED and skip the operation.
+        if (clientMonitor.interruptsPrecedingClients()) {
+            for (Operation operation : mPendingOperations) {
+                if (operation.mClientMonitor instanceof Interruptable
+                        && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) {
+                    Slog.d(getTag(), "New client incoming, marking pending client as canceling: "
+                            + operation.mClientMonitor);
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                }
             }
         }
 
@@ -490,8 +493,11 @@
         Slog.d(getTag(), "[Added] " + clientMonitor
                 + ", new queue size: " + mPendingOperations.size());
 
-        // If the current operation is cancellable, start the cancellation process.
-        if (mCurrentOperation != null && mCurrentOperation.mClientMonitor instanceof Interruptable
+        // If the new operation should interrupt preceding clients, and if the current operation is
+        // cancellable, start the cancellation process.
+        if (clientMonitor.interruptsPrecedingClients()
+                && mCurrentOperation != null
+                && mCurrentOperation.mClientMonitor instanceof Interruptable
                 && mCurrentOperation.mState == Operation.STATE_STARTED) {
             Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation);
             cancelInternal(mCurrentOperation);
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index da76af8..25b7add 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -16,9 +16,12 @@
 
 package com.android.server.biometrics.sensors;
 
+import android.annotation.NonNull;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -174,4 +177,33 @@
             mFingerprintServiceReceiver.onUdfpsPointerUp(sensorId);
         }
     }
+
+    // Face-specific callbacks for FaceManager only
+
+    /**
+     * Called each time a new frame is received during face authentication.
+     *
+     * @param frame Information about the current frame.
+     *
+     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+     */
+    public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame)
+            throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onAuthenticationFrame(frame);
+        }
+    }
+
+    /**
+     * Called each time a new frame is received during face enrollment.
+     *
+     * @param frame Information about the current frame.
+     *
+     * @throws RemoteException If the binder call to {@link IFaceServiceReceiver} fails.
+     */
+    public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) throws RemoteException {
+        if (mFaceServiceReceiver != null) {
+            mFaceServiceReceiver.onEnrollmentFrame(frame);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 8d81016..e1320d8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -113,4 +113,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_ENROLL;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index ce24e5e..de57186 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -69,6 +69,8 @@
             final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
                     ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates();
 
+            Slog.d(TAG, "Enumerate onClientFinished: " + clientMonitor + ", success: " + success);
+
             if (!unknownHALTemplates.isEmpty()) {
                 Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion");
             }
@@ -90,6 +92,7 @@
     private final Callback mRemoveCallback = new Callback() {
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success);
             mCallback.onClientFinished(InternalCleanupClient.this, success);
         }
     };
@@ -115,6 +118,8 @@
     }
 
     private void startCleanupUnknownHalTemplates() {
+        Slog.d(TAG, "startCleanupUnknownHalTemplates, size: " + mUnknownHALTemplates.size());
+
         UserTemplate template = mUnknownHALTemplates.get(0);
         mUnknownHALTemplates.remove(template);
         mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(),
@@ -138,6 +143,8 @@
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
         mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(),
                 getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId());
+
+        Slog.d(TAG, "Starting enumerate: " + mCurrentTask);
         mCurrentTask.start(mEnumerateCallback);
     }
 
@@ -165,6 +172,7 @@
                     + mCurrentTask.getClass().getSimpleName());
             return;
         }
+        Slog.d(TAG, "onEnumerated, remaining: " + remaining);
         ((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 9d19fdf..e3feb74 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -82,6 +82,7 @@
 
     private void handleEnumeratedTemplate(BiometricAuthenticator.Identifier identifier) {
         if (identifier == null) {
+            Slog.d(TAG, "Null identifier");
             return;
         }
         Slog.v(TAG, "handleEnumeratedTemplate: " + identifier.getBiometricId());
@@ -103,6 +104,7 @@
 
     private void doTemplateCleanup() {
         if (mEnrolledList == null) {
+            Slog.d(TAG, "Null enrolledList");
             return;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index d2673d2..897ebd7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -24,6 +24,8 @@
 import android.hardware.biometrics.face.AuthenticationFrame;
 import android.hardware.biometrics.face.BaseFrame;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -117,6 +119,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
 
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId,
@@ -183,7 +195,7 @@
         mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFailed();
     }
 
-    // TODO(b/174619156): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
+    // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
     @Override
     public void notifyAcquired(int userId, int acquireInfo) {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
@@ -194,7 +206,7 @@
         AuthenticationFrame authenticationFrame = new AuthenticationFrame();
         authenticationFrame.data = data;
 
-        // TODO(b/174619156): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
+        // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
         // This will need to call the correct callback once the onAcquired callback is removed.
         mSensor.getSessionForUser(userId).mHalSessionCallback.onAuthenticationFrame(
                 authenticationFrame);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 3057766..8f55402 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -29,7 +29,6 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceDataFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -179,19 +178,16 @@
         return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
     }
 
-    private boolean shouldSend(int acquireInfo, int vendorCode) {
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
-        } else {
-            return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
-        }
+    private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+        return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+                ? !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode)
+                : !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
     }
 
     @Override
     public void onAcquired(int acquireInfo, int vendorCode) {
         mLastAcquire = acquireInfo;
-
-        final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
@@ -201,9 +197,21 @@
      * @param frame Information about the current frame.
      */
     public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame) {
-        // TODO(b/178414967): Send additional frame data to the client callback.
-        final FaceDataFrame data = frame.getData();
-        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+        // Log acquisition but don't send it to the client yet, since that's handled below.
+        final int acquireInfo = frame.getData().getAcquiredInfo();
+        final int vendorCode = frame.getData().getVendorCode();
+        mLastAcquire = acquireInfo;
+        onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+        if (shouldSend && getListener() != null) {
+            try {
+                getListener().onAuthenticationFrame(frame);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to send authentication frame", e);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
     }
 
     @Override public void onLockoutTimed(long durationMillis) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index da657b9..898d81b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -27,7 +27,6 @@
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
-import android.hardware.face.FaceDataFrame;
 import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -101,14 +100,15 @@
                 getTargetUserId()).size() >= mMaxTemplatesPerUser;
     }
 
+    private boolean shouldSendAcquiredMessage(int acquireInfo, int vendorCode) {
+        return acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR
+                ? !Utils.listContains(mEnrollIgnoreListVendor, vendorCode)
+                : !Utils.listContains(mEnrollIgnoreList, acquireInfo);
+    }
+
     @Override
     public void onAcquired(int acquireInfo, int vendorCode) {
-        final boolean shouldSend;
-        if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
-            shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
-        } else {
-            shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
-        }
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
@@ -118,9 +118,20 @@
      * @param frame Information about the current frame.
      */
     public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) {
-        // TODO(b/178414967): Send additional frame data to the client callback.
-        final FaceDataFrame data = frame.getData();
-        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+        // Log acquisition but don't send it to the client yet, since that's handled below.
+        final int acquireInfo = frame.getData().getAcquiredInfo();
+        final int vendorCode = frame.getData().getVendorCode();
+        onAcquiredInternal(acquireInfo, vendorCode, false /* shouldSend */);
+
+        final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
+        if (shouldSend && getListener() != null) {
+            try {
+                getListener().onEnrollmentFrame(frame);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to send enrollment frame", e);
+                mCallback.onClientFinished(this, false /* success */);
+            }
+        }
     }
 
     @Override
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 baeb3fd..4925ce0 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
@@ -435,13 +435,7 @@
         mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> {
-            if (mTestHalEnabled) {
-                return new TestSession(mCurrentSession.mHalSessionCallback);
-            } else {
-                return mCurrentSession != null ? mCurrentSession.mSession : null;
-            }
-        };
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
 
     @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -496,6 +490,11 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        if (enabled != mTestHalEnabled) {
+            // The framework should retrieve a new session from the HAL.
+            mCurrentSession = null;
+        }
         mTestHalEnabled = enabled;
     }
 
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 1d57073..ff65c93 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
@@ -17,92 +17,119 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.Error;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.ISessionCallback;
 import android.hardware.biometrics.face.SensorProps;
 import android.hardware.common.NativeHandle;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only no-ops.
  */
 public class TestHal extends IFace.Stub {
+    private static final String TAG = "face.aidl.TestHal";
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
-            @Override
-            public void generateChallenge(int cookie, int timeoutSec) {
+        Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
 
+        return new ISession.Stub() {
+            @Override
+            public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+                cb.onChallengeGenerated(0L);
             }
 
             @Override
-            public void revokeChallenge(int cookie, long challenge) {
-
+            public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+                cb.onChallengeRevoked(challenge);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat,
                     byte enrollmentType, byte[] features, NativeHandle previewSurface) {
-                return null;
+                Slog.w(TAG, "enroll, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
-                return null;
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
-                return null;
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
-            public void enumerateEnrollments(int cookie) {
-
+            public void enumerateEnrollments(int cookie) throws RemoteException {
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsEnumerated(new int[0]);
             }
 
             @Override
-            public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+            public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsRemoved(enrollmentIds);
             }
 
             @Override
-            public void getFeatures(int cookie, int enrollmentId) {
-
+            public void getFeatures(int cookie, int enrollmentId) throws RemoteException {
+                Slog.w(TAG, "getFeatures, cookie: " + cookie);
+                cb.onFeaturesRetrieved(new byte[0], enrollmentId);
             }
 
             @Override
             public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId,
-                    byte feature, boolean enabled) {
-
+                    byte feature, boolean enabled) throws RemoteException {
+                Slog.w(TAG, "setFeature, cookie: " + cookie);
+                cb.onFeatureSet(enrollmentId, feature);
             }
 
             @Override
-            public void getAuthenticatorId(int cookie) {
-
+            public void getAuthenticatorId(int cookie) throws RemoteException {
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdRetrieved(0L);
             }
 
             @Override
-            public void invalidateAuthenticatorId(int cookie) {
-
+            public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdInvalidated(0L);
             }
 
             @Override
-            public void resetLockout(int cookie, HardwareAuthToken hat) {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+            public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
+                cb.onLockoutCleared();
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
deleted file mode 100644
index 23e6988..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestSession.java
+++ /dev/null
@@ -1,117 +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.
- */
-
-package com.android.server.biometrics.sensors.face.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.face.ISession;
-import android.hardware.common.NativeHandle;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-public class TestSession extends ISession.Stub {
-    private static final String TAG = "FaceTestSession";
-
-    @NonNull
-    private final Sensor.HalSessionCallback mHalSessionCallback;
-
-    TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
-        mHalSessionCallback = halSessionCallback;
-    }
-
-    @Override
-    public void generateChallenge(int cookie, int timeoutSec) {
-        mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
-    }
-
-    @Override
-    public void revokeChallenge(int cookie, long challenge) {
-        mHalSessionCallback.onChallengeRevoked(challenge);
-    }
-
-    @Override
-    public ICancellationSignal enroll(int cookie, HardwareAuthToken hat, byte enrollmentType,
-            byte[] features, NativeHandle previewSurface) {
-        return null;
-    }
-
-    @Override
-    public ICancellationSignal authenticate(int cookie, long operationId) {
-        return new ICancellationSignal() {
-            @Override
-            public void cancel() {
-                mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
-            }
-        };
-    }
-
-    @Override
-    public ICancellationSignal detectInteraction(int cookie) {
-        return null;
-    }
-
-    @Override
-    public void enumerateEnrollments(int cookie) {
-
-    }
-
-    @Override
-    public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
-    }
-
-    @Override
-    public void getFeatures(int cookie, int enrollmentId) {
-
-    }
-
-    @Override
-    public void setFeature(int cookie, HardwareAuthToken hat, int enrollmentId, byte feature,
-            boolean enabled) {
-
-    }
-
-    @Override
-    public void getAuthenticatorId(int cookie) {
-        Slog.d(TAG, "getAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdRetrieved(0);
-    }
-
-    @Override
-    public void invalidateAuthenticatorId(int cookie) {
-        Slog.d(TAG, "invalidateAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdInvalidated(0);
-    }
-
-    @Override
-    public void resetLockout(int cookie, HardwareAuthToken hat) {
-        mHalSessionCallback.onLockoutCleared();
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 4142a52..d519d60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -22,6 +22,8 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.face.Face;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -106,6 +108,16 @@
         public void onChallengeInterruptFinished(int sensorId) {
 
         }
+
+        @Override
+        public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
+
+        }
+
+        @Override
+        public void onEnrollmentFrame(FaceEnrollFrame frame) {
+
+        }
     };
 
     BiometricTestSessionImpl(@NonNull Context context, int sensorId, @NonNull Face10 face10,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 0b78dd0..a4b3ac5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -46,7 +46,7 @@
 
 /**
  * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 1a7544fc..fc1200a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -41,7 +41,7 @@
 
 /**
  * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
 
@@ -103,18 +103,9 @@
             disabledFeatures.add(disabledFeature);
         }
 
-        android.hardware.biometrics.face.V1_1.IBiometricsFace daemon11 =
-                android.hardware.biometrics.face.V1_1.IBiometricsFace.castFrom(getFreshDaemon());
         try {
-            final int status;
-            if (daemon11 != null) {
-                status = daemon11.enroll_1_1(token, mTimeoutSec, disabledFeatures, mSurfaceHandle);
-            } else if (mSurfaceHandle == null) {
-                status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
-            } else {
-                Slog.e(TAG, "enroll(): surface is only supported in @1.1 HAL");
-                status = BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-            }
+            final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
+
             if (status != Status.OK) {
                 onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
                 mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index c3d54c2..72c5ee5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -29,8 +29,7 @@
 
 /**
  * Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 722a3b8..b1083d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
index abfda49..1e3b92d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
@@ -33,8 +33,7 @@
 
 /**
  * Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
index 9a0974b..f2a9afc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
@@ -32,8 +32,7 @@
 
 /**
  * Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} and {@link android.hardware.biometrics.face.V1_1}
- * HIDL interfaces.
+ * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
  */
 class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
     private static final String TAG = "FaceInternalEnumerateClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
index acae899..d63791c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
     private static final String TAG = "FaceRemovalClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 14a4648..9d977d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -30,7 +30,7 @@
 
 /**
  * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
index e5edfaf..28580de 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
@@ -27,7 +27,7 @@
 
 /**
  * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index 6290e00..cc3d8f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -33,7 +33,7 @@
 
 /**
  * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * and {@link android.hardware.biometrics.face.V1_1} HIDL interfaces.
+ * HIDL interface.
  */
 public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
index bab1114d..4cdb68d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/TestHal.java
@@ -18,17 +18,19 @@
 
 import android.annotation.Nullable;
 import android.hardware.biometrics.face.V1_0.FaceError;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.biometrics.face.V1_0.OptionalBool;
 import android.hardware.biometrics.face.V1_0.OptionalUint64;
 import android.hardware.biometrics.face.V1_0.Status;
-import android.hardware.biometrics.face.V1_1.IBiometricsFace;
 import android.os.NativeHandle;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import java.util.ArrayList;
 
 public class TestHal extends IBiometricsFace.Stub {
+    private static final String TAG = "face.hidl.TestHal";
     @Nullable
     private IBiometricsFaceClientCallback mCallback;
 
@@ -47,6 +49,7 @@
 
     @Override
     public OptionalUint64 generateChallenge(int challengeTimeoutSec) {
+        Slog.w(TAG, "generateChallenge");
         final OptionalUint64 result = new OptionalUint64();
         result.status = Status.OK;
         result.value = 0;
@@ -55,6 +58,7 @@
 
     @Override
     public int enroll(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -94,17 +98,23 @@
     }
 
     @Override
-    public int enumerate() {
+    public int enumerate() throws RemoteException {
+        Slog.w(TAG, "enumerate");
+        if (mCallback != null) {
+            mCallback.onEnumerate(0 /* deviceId */, new ArrayList<>(), 0 /* userId */);
+        }
         return 0;
     }
 
     @Override
     public int remove(int faceId) {
+        Slog.w(TAG, "remove");
         return 0;
     }
 
     @Override
     public int authenticate(long operationId) {
+        Slog.w(TAG, "authenticate");
         return 0;
     }
 
@@ -115,18 +125,8 @@
 
     @Override
     public int resetLockout(ArrayList<Byte> hat) {
+        Slog.w(TAG, "resetLockout");
         return 0;
     }
 
-    @Override
-    public int enrollRemotely(ArrayList<Byte> hat, int timeoutSec,
-            ArrayList<Integer> disabledFeatures) {
-        return 0;
-    }
-
-    @Override
-    public int enroll_1_1(ArrayList<Byte> hat, int timeoutSec, ArrayList<Integer> disabledFeatures,
-            NativeHandle nativeHandle) {
-        return 0;
-    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 9611192..bcd1b8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -98,4 +98,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_DETECT_INTERACTION;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 7e4ee9e7..c83c0fb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -415,13 +415,7 @@
         mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
         mLockoutCache = new LockoutCache();
         mAuthenticatorIds = new HashMap<>();
-        mLazySession = () -> {
-            if (mTestHalEnabled) {
-                return new TestSession(mCurrentSession.mHalSessionCallback);
-            } else {
-                return mCurrentSession != null ? mCurrentSession.mSession : null;
-            }
-        };
+        mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
 
     @NonNull HalClientMonitor.LazyDaemon<ISession> getLazySession() {
@@ -476,6 +470,11 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        Slog.w(mTag, "setTestHalEnabled: " + enabled);
+        if (enabled != mTestHalEnabled) {
+            // The framework should retrieve a new session from the HAL.
+            mCurrentSession = null;
+        }
         mTestHalEnabled = enabled;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index a31bcdc..8ed24b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -17,94 +17,120 @@
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.Error;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.ISessionCallback;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IFingerprint.Stub {
+    private static final String TAG = "fingerprint.aidl.TestHal";
+
     @Override
     public SensorProps[] getSensorProps() {
+        Slog.w(TAG, "getSensorProps");
         return new SensorProps[0];
     }
 
     @Override
     public ISession createSession(int sensorId, int userId, ISessionCallback cb) {
-        return new ISession() {
-            @Override
-            public void generateChallenge(int cookie, int timeoutSec) {
+        Slog.w(TAG, "createSession, sensorId: " + sensorId + " userId: " + userId);
 
+        return new ISession.Stub() {
+            @Override
+            public void generateChallenge(int cookie, int timeoutSec) throws RemoteException {
+                Slog.w(TAG, "generateChallenge, cookie: " + cookie);
+                cb.onChallengeGenerated(0L);
             }
 
             @Override
-            public void revokeChallenge(int cookie, long challenge) {
-
+            public void revokeChallenge(int cookie, long challenge) throws RemoteException {
+                Slog.w(TAG, "revokeChallenge: " + challenge + ", cookie: " + cookie);
+                cb.onChallengeRevoked(challenge);
             }
 
             @Override
             public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
-                return null;
+                Slog.w(TAG, "enroll, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal authenticate(int cookie, long operationId) {
-                return null;
+                Slog.w(TAG, "authenticate, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
             public ICancellationSignal detectInteraction(int cookie) {
-                return null;
+                Slog.w(TAG, "detectInteraction, cookie: " + cookie);
+                return new ICancellationSignal.Stub() {
+                    @Override
+                    public void cancel() throws RemoteException {
+                        cb.onError(Error.CANCELED, 0 /* vendorCode */);
+                    }
+                };
             }
 
             @Override
-            public void enumerateEnrollments(int cookie) {
-
+            public void enumerateEnrollments(int cookie) throws RemoteException {
+                Slog.w(TAG, "enumerateEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsEnumerated(new int[0]);
             }
 
             @Override
-            public void removeEnrollments(int cookie, int[] enrollmentIds) {
-
+            public void removeEnrollments(int cookie, int[] enrollmentIds) throws RemoteException {
+                Slog.w(TAG, "removeEnrollments, cookie: " + cookie);
+                cb.onEnrollmentsRemoved(enrollmentIds);
             }
 
             @Override
-            public void getAuthenticatorId(int cookie) {
-
+            public void getAuthenticatorId(int cookie) throws RemoteException {
+                Slog.w(TAG, "getAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdRetrieved(0L);
             }
 
             @Override
-            public void invalidateAuthenticatorId(int cookie) {
-
+            public void invalidateAuthenticatorId(int cookie) throws RemoteException {
+                Slog.w(TAG, "invalidateAuthenticatorId, cookie: " + cookie);
+                cb.onAuthenticatorIdInvalidated(0L);
             }
 
             @Override
-            public void resetLockout(int cookie, HardwareAuthToken hat) {
-
+            public void resetLockout(int cookie, HardwareAuthToken hat) throws RemoteException {
+                Slog.w(TAG, "resetLockout, cookie: " + cookie);
+                cb.onLockoutCleared();
             }
 
             @Override
             public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
+                Slog.w(TAG, "onPointerDown");
             }
 
             @Override
             public void onPointerUp(int pointerId) {
-
+                Slog.w(TAG, "onPointerUp");
             }
 
             @Override
             public void onUiReady() {
-
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
+                Slog.w(TAG, "onUiReady");
             }
         };
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
deleted file mode 100644
index ac4f665..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestSession.java
+++ /dev/null
@@ -1,119 +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.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.aidl;
-
-import android.annotation.NonNull;
-import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.face.Error;
-import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.keymaster.HardwareAuthToken;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Slog;
-
-/**
- * Test session that provides mostly no-ops.
- */
-class TestSession extends ISession.Stub {
-
-    private static final String TAG = "FingerprintTestSession";
-
-    @NonNull private final Sensor.HalSessionCallback mHalSessionCallback;
-
-    TestSession(@NonNull Sensor.HalSessionCallback halSessionCallback) {
-        mHalSessionCallback = halSessionCallback;
-    }
-
-    @Override
-    public void generateChallenge(int cookie, int timeoutSec) {
-        mHalSessionCallback.onChallengeGenerated(0 /* challenge */);
-    }
-
-    @Override
-    public void revokeChallenge(int cookie, long challenge) {
-        mHalSessionCallback.onChallengeRevoked(challenge);
-    }
-
-    @Override
-    public ICancellationSignal enroll(int cookie, HardwareAuthToken hat) {
-        return null;
-    }
-
-    @Override
-    public ICancellationSignal authenticate(int cookie, long operationId) {
-        return new ICancellationSignal() {
-            @Override
-            public void cancel() {
-                mHalSessionCallback.onError(Error.CANCELED, 0 /* vendorCode */);
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return new Binder();
-            }
-        };
-    }
-
-    @Override
-    public ICancellationSignal detectInteraction(int cookie) {
-        return null;
-    }
-
-    @Override
-    public void enumerateEnrollments(int cookie) {
-        Slog.d(TAG, "enumerate");
-    }
-
-    @Override
-    public void removeEnrollments(int cookie, int[] enrollmentIds) {
-        Slog.d(TAG, "remove");
-    }
-
-    @Override
-    public void getAuthenticatorId(int cookie) {
-        Slog.d(TAG, "getAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdRetrieved(0);
-    }
-
-    @Override
-    public void invalidateAuthenticatorId(int cookie) {
-        Slog.d(TAG, "invalidateAuthenticatorId");
-        // Immediately return a value so the framework can continue with subsequent requests.
-        mHalSessionCallback.onAuthenticatorIdInvalidated(0);
-    }
-
-    @Override
-    public void resetLockout(int cookie, HardwareAuthToken hat) {
-        mHalSessionCallback.onLockoutCleared();
-    }
-
-    @Override
-    public void onPointerDown(int pointerId, int x, int y, float minor, float major) {
-
-    }
-
-    @Override
-    public void onPointerUp(int pointerId) {
-
-    }
-
-    @Override
-    public void onUiReady() {
-
-    }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 7989dca..8acb284 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -130,4 +130,9 @@
     public int getProtoEnum() {
         return BiometricsProto.CM_DETECT_INTERACTION;
     }
+
+    @Override
+    public boolean interruptsPrecedingClients() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
index b7aec0e..14fdb50 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/TestHal.java
@@ -21,11 +21,14 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
 import android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint;
 import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * Test HAL that provides only provides no-ops.
  */
 public class TestHal extends IBiometricsFingerprint.Stub {
+    private static final String TAG = "fingerprint.hidl.TestHal";
+
     @Nullable
     private IBiometricsFingerprintClientCallback mCallback;
 
@@ -57,6 +60,7 @@
 
     @Override
     public int enroll(byte[] hat, int gid, int timeoutSec) {
+        Slog.w(TAG, "enroll");
         return 0;
     }
 
@@ -79,12 +83,18 @@
     }
 
     @Override
-    public int enumerate() {
+    public int enumerate() throws RemoteException {
+        Slog.w(TAG, "Enumerate");
+        if (mCallback != null) {
+            mCallback.onEnumerate(0 /* deviceId */, 0 /* fingerId */, 0 /* groupId */,
+                    0 /* remaining */);
+        }
         return 0;
     }
 
     @Override
     public int remove(int gid, int fid) {
+        Slog.w(TAG, "Remove");
         return 0;
     }
 
@@ -95,6 +105,7 @@
 
     @Override
     public int authenticate(long operationId, int gid) {
+        Slog.w(TAG, "Authenticate");
         return 0;
     }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index e3757df..df83df9 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -16,15 +16,24 @@
 
 package com.android.server.compat;
 
+import static android.app.compat.PackageOverride.VALUE_DISABLED;
+import static android.app.compat.PackageOverride.VALUE_ENABLED;
+import static android.app.compat.PackageOverride.VALUE_UNDEFINED;
+
 import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.overrides.ChangeOverrides;
 import com.android.server.compat.overrides.OverrideValue;
+import com.android.server.compat.overrides.RawOverrideValue;
 
 import java.util.HashMap;
 import java.util.List;
@@ -36,7 +45,7 @@
  * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk}
  * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any
  * target SDK criteria set. These settings can be overridden for a specific package using
- * {@link #addPackageOverride(String, boolean)}.
+ * {@link #addPackageOverrideInternal(String, boolean)}.
  *
  * <p>Note, this class is not thread safe so callers must ensure thread safety.
  */
@@ -63,8 +72,8 @@
 
     ChangeListener mListener = null;
 
-    private Map<String, Boolean> mPackageOverrides;
-    private Map<String, Boolean> mDeferredOverrides;
+    private Map<String, Boolean> mEvaluatedOverrides;
+    private Map<String, PackageOverride> mRawOverrides;
 
     public CompatChange(long changeId) {
         this(changeId, null, -1, -1, false, false, null, false);
@@ -113,18 +122,26 @@
      * @param pname Package name to enable the change for.
      * @param enabled Whether or not to enable the change.
      */
-    void addPackageOverride(String pname, boolean enabled) {
+    private void addPackageOverrideInternal(String pname, boolean enabled) {
         if (getLoggingOnly()) {
             throw new IllegalArgumentException(
                     "Can't add overrides for a logging only change " + toString());
         }
-        if (mPackageOverrides == null) {
-            mPackageOverrides = new HashMap<>();
+        if (mEvaluatedOverrides == null) {
+            mEvaluatedOverrides = new HashMap<>();
         }
-        mPackageOverrides.put(pname, enabled);
+        mEvaluatedOverrides.put(pname, enabled);
         notifyListener(pname);
     }
 
+    private void removePackageOverrideInternal(String pname) {
+        if (mEvaluatedOverrides != null) {
+            if (mEvaluatedOverrides.remove(pname) != null) {
+                notifyListener(pname);
+            }
+        }
+    }
+
     /**
      * Tentatively set the state of this change for a given package name.
      * The override will only take effect after that package is installed, if applicable.
@@ -132,17 +149,19 @@
      * <p>Note, this method is not thread safe so callers must ensure thread safety.
      *
      * @param packageName Package name to tentatively enable the change for.
-     * @param enabled Whether or not to enable the change.
+     * @param override The package override to be set
      */
-    void addPackageDeferredOverride(String packageName, boolean enabled) {
+    void addPackageOverride(String packageName, PackageOverride override,
+            OverrideAllowedState allowedState, Context context) {
         if (getLoggingOnly()) {
             throw new IllegalArgumentException(
                     "Can't add overrides for a logging only change " + toString());
         }
-        if (mDeferredOverrides == null) {
-            mDeferredOverrides = new HashMap<>();
+        if (mRawOverrides == null) {
+            mRawOverrides = new HashMap<>();
         }
-        mDeferredOverrides.put(packageName, enabled);
+        mRawOverrides.put(packageName, override);
+        recheckOverride(packageName, allowedState, context);
     }
 
     /**
@@ -157,24 +176,44 @@
      * @return {@code true} if the recheck yielded a result that requires invalidating caches
      *         (a deferred override was consolidated or a regular override was removed).
      */
-    boolean recheckOverride(String packageName, boolean allowed) {
-        // A deferred override now is allowed by the policy, so promote it to a regular override.
-        if (hasDeferredOverride(packageName) && allowed) {
-            boolean overrideValue = mDeferredOverrides.remove(packageName);
-            addPackageOverride(packageName, overrideValue);
-            return true;
+    boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
+            Context context) {
+        boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
+
+        Long version = null;
+        try {
+            ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(
+                    packageName, 0);
+            version = applicationInfo.longVersionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Do nothing
         }
-        // A previously set override is no longer allowed by the policy, so make it deferred.
-        if (hasOverride(packageName) && !allowed) {
-            boolean overrideValue = mPackageOverrides.remove(packageName);
-            addPackageDeferredOverride(packageName, overrideValue);
-            // Notify because the override was removed.
-            notifyListener(packageName);
-            return true;
+
+        // If the app is not installed or no longer has raw overrides, evaluate to false
+        if (version == null || !hasRawOverride(packageName) || !allowed) {
+            removePackageOverrideInternal(packageName);
+            return false;
         }
-        return false;
+
+        // Evaluate the override based on its version
+        int overrideValue = mRawOverrides.get(packageName).evaluate(version);
+        switch (overrideValue) {
+            case VALUE_UNDEFINED:
+                removePackageOverrideInternal(packageName);
+                break;
+            case VALUE_ENABLED:
+                addPackageOverrideInternal(packageName, true);
+                break;
+            case VALUE_DISABLED:
+                addPackageOverrideInternal(packageName, false);
+                break;
+        }
+        return true;
     }
 
+    boolean hasPackageOverride(String pname) {
+        return mRawOverrides != null && mRawOverrides.containsKey(pname);
+    }
     /**
      * Remove any package override for the given package name, restoring the default behaviour.
      *
@@ -182,15 +221,13 @@
      *
      * @param pname Package name to reset to defaults for.
      */
-    void removePackageOverride(String pname) {
-        if (mPackageOverrides != null) {
-            if (mPackageOverrides.remove(pname) != null) {
-                notifyListener(pname);
-            }
+    boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
+            Context context) {
+        if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) {
+            recheckOverride(pname, allowedState, context);
+            return true;
         }
-        if (mDeferredOverrides != null) {
-            mDeferredOverrides.remove(pname);
-        }
+        return false;
     }
 
     /**
@@ -204,8 +241,8 @@
         if (app == null) {
             return defaultValue();
         }
-        if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) {
-            return mPackageOverrides.get(app.packageName);
+        if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) {
+            return mEvaluatedOverrides.get(app.packageName);
         }
         if (getDisabled()) {
             return false;
@@ -223,8 +260,16 @@
      * @return {@code true} if the change should be enabled for the package.
      */
     boolean willBeEnabled(String packageName) {
-        if (hasDeferredOverride(packageName)) {
-            return mDeferredOverrides.get(packageName);
+        if (hasRawOverride(packageName)) {
+            int eval = mRawOverrides.get(packageName).evaluateForAllVersions();
+            switch (eval) {
+                case VALUE_ENABLED:
+                    return true;
+                case VALUE_DISABLED:
+                    return false;
+                case VALUE_UNDEFINED:
+                    return defaultValue();
+            }
         }
         return defaultValue();
     }
@@ -243,8 +288,8 @@
      * @param packageName name of the package
      * @return true if there is such override
      */
-    boolean hasOverride(String packageName) {
-        return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
+    private boolean hasOverride(String packageName) {
+        return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName);
     }
 
     /**
@@ -252,65 +297,77 @@
      * @param packageName name of the package
      * @return true if there is such a deferred override
      */
-    boolean hasDeferredOverride(String packageName) {
-        return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
-    }
-
-    /**
-     * Checks whether a change has any package overrides.
-     * @return true if the change has at least one deferred override
-     */
-    boolean hasAnyPackageOverride() {
-        return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
-    }
-
-    /**
-     * Checks whether a change has any deferred overrides.
-     * @return true if the change has at least one deferred override
-     */
-    boolean hasAnyDeferredOverride() {
-        return mPackageOverrides != null && !mPackageOverrides.isEmpty();
+    private boolean hasRawOverride(String packageName) {
+        return mRawOverrides != null && mRawOverrides.containsKey(packageName);
     }
 
     void loadOverrides(ChangeOverrides changeOverrides) {
-        if (mDeferredOverrides == null) {
-            mDeferredOverrides = new HashMap<>();
+        if (mRawOverrides == null) {
+            mRawOverrides = new HashMap<>();
         }
-        mDeferredOverrides.clear();
-        for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
-            mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
+        mRawOverrides.clear();
+
+        if (mEvaluatedOverrides == null) {
+            mEvaluatedOverrides = new HashMap<>();
+        }
+        mEvaluatedOverrides.clear();
+
+        // Load deferred overrides for backwards compatibility
+        if (changeOverrides.getDeferred() != null) {
+            for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
+                mRawOverrides.put(override.getPackageName(),
+                        new PackageOverride.Builder().setEnabled(
+                                override.getEnabled()).build());
+            }
         }
 
-        if (mPackageOverrides == null) {
-            mPackageOverrides = new HashMap<>();
+        // Load validated overrides. For backwards compatibility, we also add them to raw overrides.
+        if (changeOverrides.getValidated() != null) {
+            for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
+                mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled());
+                mRawOverrides.put(override.getPackageName(),
+                        new PackageOverride.Builder().setEnabled(
+                                override.getEnabled()).build());
+            }
         }
-        mPackageOverrides.clear();
-        for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
-            mPackageOverrides.put(override.getPackageName(), override.getEnabled());
+
+        // Load raw overrides
+        if (changeOverrides.getRaw() != null) {
+            for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) {
+                PackageOverride packageOverride = new PackageOverride.Builder()
+                        .setMinVersionCode(override.getMinVersionCode())
+                        .setMaxVersionCode(override.getMaxVersionCode())
+                        .setEnabled(override.getEnabled())
+                        .build();
+                mRawOverrides.put(override.getPackageName(), packageOverride);
+            }
         }
     }
 
     ChangeOverrides saveOverrides() {
-        if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
+        if (mRawOverrides == null || mRawOverrides.isEmpty()) {
             return null;
         }
         ChangeOverrides changeOverrides = new ChangeOverrides();
         changeOverrides.setChangeId(getId());
-        ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
-        List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
-        if (mDeferredOverrides != null) {
-            for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
-                OverrideValue override = new OverrideValue();
+        ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw();
+        List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue();
+        if (mRawOverrides != null) {
+            for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) {
+                RawOverrideValue override = new RawOverrideValue();
                 override.setPackageName(entry.getKey());
-                override.setEnabled(entry.getValue());
-                deferredList.add(override);
+                override.setMinVersionCode(entry.getValue().getMinVersionCode());
+                override.setMaxVersionCode(entry.getValue().getMaxVersionCode());
+                override.setEnabled(entry.getValue().getEnabled());
+                rawList.add(override);
             }
         }
-        changeOverrides.setDeferred(deferredOverrides);
+        changeOverrides.setRaw(rawOverrides);
+
         ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
         List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
-        if (mPackageOverrides != null) {
-            for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
+        if (mEvaluatedOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) {
                 OverrideValue override = new OverrideValue();
                 override.setPackageName(entry.getKey());
                 override.setEnabled(entry.getValue());
@@ -337,11 +394,11 @@
         if (getLoggingOnly()) {
             sb.append("; loggingOnly");
         }
-        if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
-            sb.append("; packageOverrides=").append(mPackageOverrides);
+        if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) {
+            sb.append("; packageOverrides=").append(mEvaluatedOverrides);
         }
-        if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
-            sb.append("; deferredOverrides=").append(mDeferredOverrides);
+        if (mRawOverrides != null && mRawOverrides.size() > 0) {
+            sb.append("; rawOverrides=").append(mRawOverrides);
         }
         if (getOverridable()) {
             sb.append("; overridable");
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 6b77b9d..422991e 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,6 +17,7 @@
 package com.android.server.compat;
 
 import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -31,6 +32,7 @@
 import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
@@ -70,11 +72,13 @@
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
     private final OverrideValidatorImpl mOverrideValidator;
+    private Context mContext;
     private File mOverridesFile;
 
     @VisibleForTesting
     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
         mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
+        mContext = context;
     }
 
     static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -210,17 +214,33 @@
      * @throws IllegalStateException if overriding is not allowed
      */
     boolean addOverride(long changeId, String packageName, boolean enabled) {
-        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
+        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
+                new PackageOverride.Builder().setEnabled(enabled).build());
         saveOverrides();
         invalidateCache();
         return alreadyKnown;
     }
 
     /**
-     * Unsafe version of {@link #addOverride(long, String, boolean)}.
-     * It does not invalidate the cache nor save the overrides.
+     * Overrides the enabled state for a given change and app.
+     *
+     * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+     *
+     * @param overrides   list of overrides to default changes config.
+     * @param packageName app for which the overrides will be applied.
      */
-    private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
+    void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
+        synchronized (mChanges) {
+            for (Long changeId : overrides.overrides.keySet()) {
+                addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
+            }
+            saveOverrides();
+            invalidateCache();
+        }
+    }
+
+    private boolean addOverrideUnsafe(long changeId, String packageName,
+            PackageOverride overrides) {
         boolean alreadyKnown = true;
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -232,17 +252,8 @@
                 c = new CompatChange(changeId);
                 addChange(c);
             }
-            switch (allowedState.state) {
-                case OverrideAllowedState.ALLOWED:
-                    c.addPackageOverride(packageName, enabled);
-                    break;
-                case OverrideAllowedState.DEFERRED_VERIFICATION:
-                    c.addPackageDeferredOverride(packageName, enabled);
-                    break;
-                default:
-                    throw new IllegalStateException("Should only be able to override changes that "
-                            + "are allowed or can be deferred.");
-            }
+            c.addPackageOverride(packageName, overrides, allowedState, mContext);
+            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -311,47 +322,20 @@
      * It does not invalidate the cache nor save the overrides.
      */
     private boolean removeOverrideUnsafe(long changeId, String packageName) {
-        boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
             if (c != null) {
-                // Always allow removing a deferred override.
-                if (c.hasDeferredOverride(packageName)) {
-                    c.removePackageOverride(packageName);
-                    overrideExists = true;
-                } else if (c.hasOverride(packageName)) {
-                    // Regular overrides need to pass the policy.
-                    overrideExists = true;
-                    OverrideAllowedState allowedState =
-                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                OverrideAllowedState allowedState =
+                        mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                if (c.hasPackageOverride(packageName)) {
                     allowedState.enforce(changeId, packageName);
-                    c.removePackageOverride(packageName);
+                    c.removePackageOverride(packageName, allowedState, mContext);
+                    invalidateCache();
+                    return true;
                 }
             }
         }
-        return overrideExists;
-    }
-
-    /**
-     * Overrides the enabled state for a given change and app.
-     *
-     * <p>Note: package overrides are not persistent and will be lost on system or runtime restart.
-     *
-     * @param overrides   list of overrides to default changes config
-     * @param packageName app for which the overrides will be applied
-     */
-    void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
-        synchronized (mChanges) {
-            for (Long changeId : overrides.enabledChanges()) {
-                addOverrideUnsafe(changeId, packageName, true);
-            }
-            for (Long changeId : overrides.disabledChanges()) {
-                addOverrideUnsafe(changeId, packageName, false);
-
-            }
-            saveOverrides();
-            invalidateCache();
-        }
+        return false;
     }
 
     /**
@@ -402,7 +386,8 @@
     int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverrideUnsafe(changeId, packageName, true);
+            addOverrideUnsafe(changeId, packageName,
+                    new PackageOverride.Builder().setEnabled(true).build());
         }
         saveOverrides();
         invalidateCache();
@@ -418,7 +403,8 @@
     int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverrideUnsafe(changeId, packageName, false);
+            addOverrideUnsafe(changeId, packageName,
+                    new PackageOverride.Builder().setEnabled(false).build());
         }
         saveOverrides();
         invalidateCache();
@@ -615,8 +601,7 @@
                 CompatChange c = mChanges.valueAt(idx);
                 OverrideAllowedState allowedState =
                         mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
-                boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED);
-                shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride);
+                shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext);
             }
             if (shouldInvalidateCache) {
                 invalidateCache();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 51ba5f7..d17753f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -25,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.app.compat.PackageOverride;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +44,7 @@
 import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
@@ -51,6 +53,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * System server internal API for gating and reporting compatibility changes.
@@ -161,6 +165,22 @@
     @Override
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
         checkCompatChangeOverridePermission();
+        Map<Long, PackageOverride> overridesMap = new HashMap<>();
+        for (long change : overrides.enabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+        }
+        for (long change : overrides.disabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+                    .build());
+        }
+        mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
+        killPackage(packageName);
+    }
+
+    @Override
+    public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides,
+            String packageName) {
+        checkCompatChangeOverridePermission();
         mCompatConfig.addOverrides(overrides, packageName);
         killPackage(packageName);
     }
@@ -168,7 +188,15 @@
     @Override
     public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
         checkCompatChangeOverridePermission();
-        mCompatConfig.addOverrides(overrides, packageName);
+        Map<Long, PackageOverride> overridesMap = new HashMap<>();
+        for (long change : overrides.enabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
+        }
+        for (long change : overrides.disabledChanges()) {
+            overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
+                    .build());
+        }
+        mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 397af7b..61b11a5 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,7 +16,6 @@
 
 package com.android.server.connectivity;
 
-import static android.net.NetworkCapabilities.MAX_TRANSPORT;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -25,15 +24,14 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
-import android.net.ConnectivityManager;
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
 import android.net.metrics.DnsEvent;
-import android.net.metrics.ConnectStats;
 import android.net.metrics.IpManagerEvent;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.metrics.NetworkEvent;
@@ -41,12 +39,13 @@
 import android.net.metrics.ValidationProbeEvent;
 import android.net.metrics.WakeupStats;
 import android.os.Parcelable;
-import android.util.SparseArray;
 import android.util.SparseIntArray;
+
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -361,29 +360,22 @@
                 return IpConnectivityLogClass.UNKNOWN;
             case 1:
                 int t = Long.numberOfTrailingZeros(transports);
-                return transportToLinkLayer(t);
+                return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN);
             default:
                 return IpConnectivityLogClass.MULTIPLE;
         }
     }
 
-    private static int transportToLinkLayer(int transport) {
-        if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
-            return TRANSPORT_LINKLAYER_MAP[transport];
-        }
-        return IpConnectivityLogClass.UNKNOWN;
-    }
-
-    private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+    private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray();
     static {
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR]   = IpConnectivityLogClass.CELLULAR;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI]       = IpConnectivityLogClass.WIFI;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH]  = IpConnectivityLogClass.BLUETOOTH;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET]   = IpConnectivityLogClass.ETHERNET;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN]        = IpConnectivityLogClass.UNKNOWN;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN;
-        TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN]     = IpConnectivityLogClass.LOWPAN;
-    };
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN);
+        TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN);
+    }
 
     private static int ifnameToLinkLayer(String ifname) {
         // Do not try to catch all interface names with regexes, instead only catch patterns that
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index bff1a5c..c05e253 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -717,8 +717,9 @@
                 mNumBackgroundNetworkRequests += delta;
                 break;
 
-            case TRACK_DEFAULT:
             case LISTEN:
+            case TRACK_DEFAULT:
+            case TRACK_SYSTEM_DEFAULT:
                 break;
 
             case NONE:
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 3d71b0a..508739f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -79,7 +79,6 @@
     // server.
     public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS";
     public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS";
-    public static final String NOTIFICATION_CHANNEL_VPN = "VPN";
 
     // The context is for the current user (system server)
     private final Context mContext;
@@ -161,13 +160,20 @@
         if (nai != null) {
             transportType = approximateTransportType(nai);
             final String extraInfo = nai.networkInfo.getExtraInfo();
-            name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo;
+            if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null
+                    && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
+                    .getVenueFriendlyName())) {
+                name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+            } else {
+                name = TextUtils.isEmpty(extraInfo)
+                        ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+            }
             // Only notify for Internet-capable networks.
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
         } else {
             // Legacy notifications.
             transportType = TRANSPORT_CELLULAR;
-            name = null;
+            name = "";
         }
 
         // Clear any previous notification with lower priority, otherwise return. http://b/63676954.
@@ -193,35 +199,30 @@
         final CharSequence details;
         int icon = getIcon(transportType);
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
             if (transportType == TRANSPORT_CELLULAR) {
                 title = r.getString(R.string.mobile_no_internet);
             } else if (transportType == TRANSPORT_WIFI) {
-                title = r.getString(R.string.wifi_no_internet,
-                        WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                title = r.getString(R.string.wifi_no_internet, name);
             } else {
                 title = r.getString(R.string.other_networks_no_internet);
             }
             details = r.getString(R.string.private_dns_broken_detailed);
         } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
                 && transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.network_partial_connectivity,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.network_partial_connectivity, name);
             details = r.getString(R.string.network_partial_connectivity_detailed);
         } else if (notifyType == NotificationType.LOST_INTERNET &&
                 transportType == TRANSPORT_WIFI) {
-            title = r.getString(R.string.wifi_no_internet,
-                    WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+            title = r.getString(R.string.wifi_no_internet, name);
             details = r.getString(R.string.wifi_no_internet_detailed);
         } else if (notifyType == NotificationType.SIGN_IN) {
             switch (transportType) {
                 case TRANSPORT_WIFI:
                     title = r.getString(R.string.wifi_available_sign_in, 0);
-                    details = r.getString(R.string.network_available_sign_in_detailed,
-                            WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()));
+                    details = r.getString(R.string.network_available_sign_in_detailed, name);
                     break;
                 case TRANSPORT_CELLULAR:
                     title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 87b4c16..7ef315c 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,7 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.telephony.data.EpsBearerQosSessionAttributes;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.ConnectivityService;
@@ -260,18 +260,18 @@
     }
 
     private static void log(final String msg) {
-        Slog.d(TAG, msg);
+        Log.d(TAG, msg);
     }
 
     private static void logw(final String msg) {
-        Slog.w(TAG, msg);
+        Log.w(TAG, msg);
     }
 
     private static void loge(final String msg) {
-        Slog.e(TAG, msg);
+        Log.e(TAG, msg);
     }
 
     private static void logwtf(final String msg) {
-        Slog.wtf(TAG, msg);
+        Log.wtf(TAG, msg);
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index b5f20d7..c480594 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -41,7 +41,6 @@
 import android.os.MessageQueue;
 import android.os.Messenger;
 import android.system.ErrnoException;
-import android.system.Int32Ref;
 import android.system.Os;
 import android.util.Log;
 import android.util.SparseArray;
@@ -306,9 +305,8 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCINQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCINQ);
+        if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
         }
@@ -317,9 +315,8 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        Int32Ref result = new Int32Ref(-1);
-        Os.ioctlInt(fd, SIOCOUTQ, result);
-        if (result.value != 0) {
+        final int result = Os.ioctlInt(fd, SIOCOUTQ);
+        if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
         }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index fc2c7e0..a769e88 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -21,10 +21,10 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -74,6 +74,7 @@
 import android.net.UnderlyingNetworkInfo;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.ChildSessionParams;
@@ -171,6 +172,12 @@
      */
     @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB
 
+    /**
+     * Network score that VPNs will announce to ConnectivityService.
+     * TODO: remove when the network scoring refactor lands.
+     */
+    private static final int VPN_DEFAULT_SCORE = 101;
+
     // TODO: create separate trackers for each unique VPN to support
     // automated reconnection
 
@@ -435,6 +442,7 @@
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
 
         loadAlwaysOnPackage(keyStore);
     }
@@ -494,6 +502,11 @@
         updateAlwaysOnNotification(detailedState);
     }
 
+    private void resetNetworkCapabilities() {
+        mNetworkCapabilities.setUids(null);
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
+    }
+
     /**
      * Chooses whether to force all connections to go though VPN.
      *
@@ -518,6 +531,11 @@
         }
     }
 
+    /** Returns the package name that is currently prepared. */
+    public String getPackage() {
+        return mPackage;
+    }
+
     /**
      * Check whether to prevent all traffic outside of a VPN even when the VPN is not connected.
      *
@@ -928,7 +946,7 @@
                 agentDisconnect();
                 jniReset(mInterface);
                 mInterface = null;
-                mNetworkCapabilities.setUids(null);
+                resetNetworkCapabilities();
             }
 
             // Revoke the connection or stop the VpnRunner.
@@ -999,6 +1017,8 @@
                 case VpnManager.TYPE_VPN_SERVICE:
                     toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN};
                     break;
+                case VpnManager.TYPE_VPN_LEGACY:
+                    return false;
                 default:
                     Log.wtf(TAG, "Unrecognized VPN type while granting authorization");
                     return false;
@@ -1029,6 +1049,8 @@
                 return isVpnServicePreConsented(context, packageName);
             case VpnManager.TYPE_VPN_PLATFORM:
                 return isVpnProfilePreConsented(context, packageName);
+            case VpnManager.TYPE_VPN_LEGACY:
+                return VpnConfig.LEGACY_VPN.equals(packageName);
             default:
                 return false;
         }
@@ -1211,6 +1233,8 @@
         mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
+        mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
+
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
         if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) {
@@ -1220,8 +1244,7 @@
         }
 
         mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
-                mNetworkCapabilities, lp,
-                ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
+                mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) {
             @Override
             public void unwanted() {
                 // We are user controlled, not driven by NetworkRequest.
@@ -1735,7 +1758,7 @@
 
     private void cleanupVpnStateLocked() {
         mStatusIntent = null;
-        mNetworkCapabilities.setUids(null);
+        resetNetworkCapabilities();
         mConfig = null;
         mInterface = null;
 
@@ -1846,22 +1869,18 @@
     }
 
     /**
-     * Gets the currently running App-based VPN type
+     * Gets the currently running VPN type
      *
-     * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an
-     *     app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
+     * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a
+     *     VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
      *     Settings-based, the Platform VPNs can be initiated by both apps and Settings.
      */
-    public synchronized int getActiveAppVpnType() {
-        if (VpnConfig.LEGACY_VPN.equals(mPackage)) {
-            return VpnManager.TYPE_VPN_NONE;
-        }
-
-        if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) {
-            return VpnManager.TYPE_VPN_PLATFORM;
-        } else {
-            return VpnManager.TYPE_VPN_SERVICE;
-        }
+    public synchronized int getActiveVpnType() {
+        if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE;
+        if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE;
+        return mVpnRunner instanceof IkeV2VpnRunner
+                ? VpnManager.TYPE_VPN_PLATFORM
+                : VpnManager.TYPE_VPN_LEGACY;
     }
 
     private void updateAlwaysOnNotification(DetailedState networkState) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index e496d77..e693bcc 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -16,9 +16,14 @@
 
 package com.android.server.devicestate;
 
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -35,24 +40,25 @@
  */
 public final class DeviceState {
     /** Unique identifier for the device state. */
-    @IntRange(from = 0)
+    @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
     private final int mIdentifier;
 
     /** String description of the device state. */
     @NonNull
     private final String mName;
 
-    public DeviceState(@IntRange(from = 0) int identifier,
+    public DeviceState(
+            @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
             @NonNull String name) {
-        if (identifier < 0) {
-            throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
-        }
+        Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
+                "identifier");
+
         mIdentifier = identifier;
         mName = name;
     }
 
     /** Returns the unique identifier for the device state. */
-    @IntRange(from = 0)
+    @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
     public int getIdentifier() {
         return mIdentifier;
     }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 984a176..b3a6f26 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
 package com.android.server.devicestate;
 
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
 
 import android.annotation.IntRange;
@@ -89,7 +91,7 @@
     // the current state after the initial callback from the DeviceStateProvider.
     @GuardedBy("mLock")
     @NonNull
-    private DeviceState mCommittedState = new DeviceState(0, "UNSET");
+    private DeviceState mCommittedState = new DeviceState(MINIMUM_DEVICE_STATE, "UNSET");
     // The device state that is currently awaiting callback from the policy to be committed.
     @GuardedBy("mLock")
     @NonNull
@@ -598,8 +600,9 @@
         }
 
         @Override
-        public void onStateChanged(@IntRange(from = 0) int identifier) {
-            if (identifier < 0) {
+        public void onStateChanged(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier) {
+            if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
                 throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 2d4377f..109bf63 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.devicestate;
 
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
 import android.annotation.IntRange;
 
 /**
@@ -65,8 +68,10 @@
          *
          * @param identifier the identifier of the new device state.
          *
-         * @throws IllegalArgumentException if the state is less than 0.
+         * @throws IllegalArgumentException if the state is less than {@link MINIMUM_DEVICE_STATE}
+         * or greater than {@link MAXIMUM_DEVICE_STATE}.
          */
-        void onStateChanged(@IntRange(from = 0) int identifier);
+        void onStateChanged(
+                @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier);
     }
 }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index fa063b2..225da7a 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -42,8 +42,8 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 4832e46..a62f67a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -28,8 +28,8 @@
 import android.util.Slog;
 import android.util.Spline;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.Preconditions;
 import com.android.server.display.utils.Plog;
 
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
new file mode 100644
index 0000000..d4556ed
--- /dev/null
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayAddress;
+
+import com.android.server.display.layout.Layout;
+
+import java.util.Arrays;
+
+/**
+ * Mapping from device states into {@link Layout}s. This allows us to map device
+ * states into specific layouts for the connected displays; particularly useful for
+ * foldable and multi-display devices where the default display and which displays are ON can
+ * change depending on the state of the device.
+ */
+class DeviceStateToLayoutMap {
+    private static final String TAG = "DeviceStateToLayoutMap";
+
+    public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+
+    // TODO - b/168208162 - Remove these when we check in static definitions for layouts
+    public static final int STATE_FOLDED = 100;
+    public static final int STATE_UNFOLDED = 101;
+
+    private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+
+    DeviceStateToLayoutMap(Context context) {
+        mLayoutMap.append(STATE_DEFAULT, new Layout());
+        loadFoldedDisplayConfig(context);
+    }
+
+    public void dumpLocked(IndentingPrintWriter ipw) {
+        ipw.println("DeviceStateToLayoutMap:");
+        ipw.increaseIndent();
+
+        ipw.println("Registered Layouts:");
+        for (int i = 0; i < mLayoutMap.size(); i++) {
+            ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
+        }
+    }
+
+    Layout get(int state) {
+        Layout layout = mLayoutMap.get(state);
+        if (layout == null) {
+            layout = mLayoutMap.get(STATE_DEFAULT);
+        }
+        return layout;
+    }
+
+    private Layout create(int state) {
+        if (mLayoutMap.contains(state)) {
+            Slog.e(TAG, "Attempted to create a second layout for state " + state);
+            return null;
+        }
+
+        final Layout layout = new Layout();
+        mLayoutMap.append(state, layout);
+        return layout;
+    }
+
+    private void loadFoldedDisplayConfig(Context context) {
+        final String[] strDisplayIds = context.getResources().getStringArray(
+                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
+
+        if (strDisplayIds.length != 2 || TextUtils.isEmpty(strDisplayIds[0])
+                || TextUtils.isEmpty(strDisplayIds[1])) {
+            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(strDisplayIds)
+                    + "]");
+            return;
+        }
+
+        final long[] displayIds;
+        try {
+            displayIds = new long[] {
+                Long.parseLong(strDisplayIds[0]),
+                Long.parseLong(strDisplayIds[1])
+            };
+        } catch (NumberFormatException nfe) {
+            Slog.w(TAG, "Folded display config non numerical: " + Arrays.toString(strDisplayIds));
+            return;
+        }
+
+        final int[] foldedDeviceStates = context.getResources().getIntArray(
+                com.android.internal.R.array.config_foldedDeviceStates);
+        // Only add folded states if folded state config is not empty
+        if (foldedDeviceStates.length == 0) {
+            return;
+        }
+
+        // Create the folded state layout
+        final Layout foldedLayout = create(STATE_FOLDED);
+        foldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[0]), true /*isDefault*/);
+
+        // Create the unfolded state layout
+        final Layout unfoldedLayout = create(STATE_UNFOLDED);
+        unfoldedLayout.createDisplayLocked(
+                DisplayAddress.fromPhysicalDisplayId(displayIds[1]), true /*isDefault*/);
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d687221..1b25427 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -23,8 +23,8 @@
 import android.util.Slog;
 import android.view.DisplayAddress;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index bf16a6d..501533d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,7 +26,7 @@
 import android.view.RoundedCorners;
 import android.view.Surface;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.util.Arrays;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 5c0fceb..57f4486 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DisplayAddress;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -112,12 +113,11 @@
         }
     }
 
-    public DisplayDevice getByIdLocked(@NonNull String uniqueId) {
-        final int count = mDisplayDevices.size();
-        for (int i = 0; i < count; i++) {
-            final DisplayDevice d = mDisplayDevices.get(i);
-            if (uniqueId.equals(d.getUniqueId())) {
-                return d;
+    public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
+        for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+            final DisplayDevice device = mDisplayDevices.get(i);
+            if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+                return device;
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 01fee56..6a22941 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -106,9 +106,9 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.AnimationThread;
@@ -1188,6 +1188,7 @@
             final LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
             final int state;
             final int displayId = display.getDisplayIdLocked();
+
             if (display.isEnabled()) {
                 state = mDisplayStates.get(displayId);
             } else {
@@ -1564,12 +1565,6 @@
         }
     }
 
-    void setFoldOverride(Boolean isFolded) {
-        synchronized (mSyncRoot) {
-            mLogicalDisplayMapper.setFoldOverrideLocked(isFolded);
-        }
-    }
-
     private void clearViewportsLocked() {
         mViewports.clear();
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index aaea15a..d1d0496 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@
                 return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
-            case "set-fold":
-                return setFoldOverride();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -92,8 +90,6 @@
         pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
-        pw.println("  set-fold [fold|unfold|reset]");
-        pw.println("    Simulates the 'fold' state of a device. 'reset' for default behavior.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -165,26 +161,4 @@
         mService.setAmbientColorTemperatureOverride(cct);
         return 0;
     }
-
-    private int setFoldOverride() {
-        String state = getNextArg();
-        if (state == null) {
-            getErrPrintWriter().println("Error: no parameter specified for set-fold");
-            return 1;
-        }
-        final Boolean isFolded;
-        if ("fold".equals(state)) {
-            isFolded = true;
-        } else if ("unfold".equals(state)) {
-            isFolded = false;
-        } else if ("reset".equals(state)) {
-            isFolded = null;
-        } else {
-            getErrPrintWriter().println("Error: Invalid fold state request: " + state);
-            return 1;
-        }
-
-        mService.setFoldOverride(isFolded);
-        return 0;
-    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2df33652..9320f50 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -53,8 +53,8 @@
 import android.util.TimeUtils;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 173adce..1d20d87 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,7 +26,7 @@
 import android.view.Choreographer;
 import android.view.Display;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 73ebb2e..3b66236 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,7 +38,7 @@
 import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 80781d2..20b133c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -32,6 +32,7 @@
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -718,6 +719,7 @@
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
         pw.println("mLayerStack=" + mLayerStack);
+        pw.println("mIsEnabled=" + mIsEnabled);
         pw.println("mHasContent=" + mHasContent);
         pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
@@ -731,4 +733,11 @@
         pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
         pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
     }
+
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        dumpLocked(new PrintWriter(sw));
+        return sw.toString();
+    }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 16c4b26..a054db5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -27,10 +27,10 @@
 import android.view.DisplayEventReceiver;
 import android.view.DisplayInfo;
 
+import com.android.server.display.layout.Layout;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -75,40 +75,25 @@
     private final boolean mSingleDisplayDemoMode;
 
     /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code true}.
-     */
-    private String mDisplayIdToUseWhenFolded;
-
-    /**
-     * Physical Display ID of the DisplayDevice to associate with the default LogicalDisplay
-     * when {@link mIsFolded} is set to {@code false}.
-     */
-    private String mDisplayIdToUseWhenUnfolded;
-
-    /** Overrides the folded state of the device. For use with ADB commands. */
-    private Boolean mIsFoldedOverride;
-
-    /** Saves the last device fold state. */
-    private boolean mIsFolded;
-
-    /**
      * List of all logical displays indexed by logical display id.
      * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
      * TODO: multi-display - Move the aforementioned comment?
      */
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
-    private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
-    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
 
     /** A mapping from logical display id to display group. */
     private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
+    private final DeviceStateToLayoutMap mDeviceStateToLayoutMap;
     private final Listener mListener;
     private final int[] mFoldedDeviceStates;
 
+    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+    private Layout mCurrentLayout = null;
+    private boolean mIsFolded = false;
+
     LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
         mDisplayDeviceRepo = repo;
         mListener = listener;
@@ -118,7 +103,7 @@
         mFoldedDeviceStates = context.getResources().getIntArray(
                 com.android.internal.R.array.config_foldedDeviceStates);
 
-        loadFoldedDisplayConfig(context);
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(context);
     }
 
     @Override
@@ -213,13 +198,12 @@
         ipw.increaseIndent();
 
         ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
-        ipw.println("mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+        ipw.println("mCurrentLayout=" + mCurrentLayout);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
         ipw.println();
         ipw.println("Logical Displays: size=" + logicalDisplayCount);
-
-
         for (int i = 0; i < logicalDisplayCount; i++) {
             int displayId = mLogicalDisplays.keyAt(i);
             LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -229,6 +213,7 @@
             ipw.decreaseIndent();
             ipw.println();
         }
+        mDeviceStateToLayoutMap.dumpLocked(ipw);
     }
 
     void setDeviceStateLocked(int state) {
@@ -244,79 +229,55 @@
 
     void setDeviceFoldedLocked(boolean isFolded) {
         mIsFolded = isFolded;
-        if (mIsFoldedOverride != null) {
-            isFolded = mIsFoldedOverride.booleanValue();
+
+        // Until we have fully functioning state mapping, use hardcoded states based on isFolded
+        final int state = mIsFolded ? DeviceStateToLayoutMap.STATE_FOLDED
+                : DeviceStateToLayoutMap.STATE_UNFOLDED;
+
+        if (DEBUG) {
+            Slog.d(TAG, "New device state: " + state);
         }
 
-        if (mDisplayIdToUseWhenFolded == null || mDisplayIdToUseWhenUnfolded == null
-                || mLogicalDisplays.size() < 2) {
-            // Do nothing if this behavior is disabled or there are less than two displays.
+        final Layout layout = mDeviceStateToLayoutMap.get(state);
+        if (layout == null) {
+            return;
+        }
+        final Layout.Display displayLayout = layout.getById(Display.DEFAULT_DISPLAY);
+        if (displayLayout == null) {
+            return;
+        }
+        final DisplayDevice newDefaultDevice =
+                mDisplayDeviceRepo.getByAddressLocked(displayLayout.getAddress());
+        if (newDefaultDevice == null) {
             return;
         }
 
-        final DisplayDevice deviceFolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenFolded);
-        final DisplayDevice deviceUnfolded =
-                mDisplayDeviceRepo.getByIdLocked(mDisplayIdToUseWhenUnfolded);
-        if (deviceFolded == null || deviceUnfolded == null) {
-            // If the expected devices for folding functionality are not present, return early.
-            return;
-        }
-
-        // Find the associated LogicalDisplays for the configured "folding" DeviceDisplays.
-        final LogicalDisplay displayFolded = getLocked(deviceFolded);
-        final LogicalDisplay displayUnfolded = getLocked(deviceUnfolded);
-        if (displayFolded == null || displayUnfolded == null) {
-            // If the expected displays are not present, return early.
-            return;
-        }
-
-        // Find out which display is currently default and which is disabled.
         final LogicalDisplay defaultDisplay = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
-        final LogicalDisplay disabledDisplay;
-        if (defaultDisplay == displayFolded) {
-            disabledDisplay = displayUnfolded;
-        } else if (defaultDisplay == displayUnfolded) {
-            disabledDisplay = displayFolded;
-        } else {
-            // If neither folded or unfolded displays are currently set to the default display, we
-            // are in an unknown state and it's best to log the error and bail.
-            Slog.e(TAG, "Unexpected: when attempting to swap displays, neither of the two"
-                    + " configured displays were set up as the default display. Default: "
-                    + defaultDisplay.getDisplayInfoLocked() + ",  ConfiguredDisplays: [ folded="
-                    + displayFolded.getDisplayInfoLocked() + ", unfolded="
-                    + displayUnfolded.getDisplayInfoLocked() + " ]");
+        mCurrentLayout = layout;
+
+        // If we're already set up accurately, return early
+        if (defaultDisplay.getPrimaryDisplayDeviceLocked() == newDefaultDevice) {
             return;
         }
 
-        if (isFolded == (defaultDisplay == displayFolded)) {
-            // Nothing to do, already in the right state.
+        // We need to swap the default display's display-device with the one that is supposed
+        // to be the default in the new layout.
+        final LogicalDisplay displayToSwap = getLocked(newDefaultDevice);
+        if (displayToSwap == null) {
+            Slog.w(TAG, "Canceling display swap - unexpected empty second display for: "
+                    + newDefaultDevice);
             return;
         }
-
-        // Everything was checked and we need to swap, lets swap.
-        displayFolded.swapDisplaysLocked(displayUnfolded);
+        defaultDisplay.swapDisplaysLocked(displayToSwap);
 
         // We ensure that the non-default Display is always forced to be off. This was likely
         // already done in a previous iteration, but we do it with each swap in case something in
         // the underlying LogicalDisplays changed: like LogicalDisplay recreation, for example.
         defaultDisplay.setEnabled(true);
-        disabledDisplay.setEnabled(false);
+        displayToSwap.setEnabled(false);
 
         // Update the world
         updateLogicalDisplaysLocked();
-
-        if (DEBUG) {
-            Slog.d(TAG, "Folded displays: isFolded: " + isFolded + ", defaultDisplay? "
-                    + defaultDisplay.getDisplayInfoLocked());
-        }
-    }
-
-    void setFoldOverrideLocked(Boolean isFolded) {
-        if (!Objects.equals(isFolded, mIsFoldedOverride)) {
-            mIsFoldedOverride = isFolded;
-            setDeviceFoldedLocked(mIsFolded);
-        }
     }
 
     private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
@@ -333,7 +294,7 @@
             return;
         }
 
-        final int displayId = assignDisplayIdLocked(isDefault);
+        final int displayId = Layout.assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
         final DisplayGroup displayGroup;
@@ -356,8 +317,15 @@
             return;
         }
 
-        mLogicalDisplays.put(displayId, display);
+        // For foldable devices, we start the internal non-default displays as disabled.
+        // TODO - b/168208162 - this will be removed when we recalculate the layout with each
+        // display-device addition.
+        if (mFoldedDeviceStates.length > 0 && deviceInfo.type == Display.TYPE_INTERNAL
+                && !isDefault) {
+            display.setEnabled(false);
+        }
 
+        mLogicalDisplays.put(displayId, display);
         displayGroup.addDisplayLocked(display);
         mDisplayIdToGroupMap.append(displayId, displayGroup);
 
@@ -375,6 +343,10 @@
             mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
                     LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "New Display added: " + display);
+        }
     }
 
     /**
@@ -466,10 +438,6 @@
         }
     }
 
-    private int assignDisplayIdLocked(boolean isDefault) {
-        return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
-    }
-
     private int assignDisplayGroupIdLocked(boolean isDefault) {
         return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
     }
@@ -480,21 +448,6 @@
         return displayId;
     }
 
-    private void loadFoldedDisplayConfig(Context context) {
-        final String[] displayIds = context.getResources().getStringArray(
-                com.android.internal.R.array.config_internalFoldedPhysicalDisplayIds);
-
-        if (displayIds.length != 2 || TextUtils.isEmpty(displayIds[0])
-                || TextUtils.isEmpty(displayIds[1])) {
-            Slog.w(TAG, "Folded display configuration invalid: [" + Arrays.toString(displayIds)
-                    + "]");
-            return;
-        }
-
-        mDisplayIdToUseWhenFolded = displayIds[0];
-        mDisplayIdToUseWhenUnfolded = displayIds[1];
-    }
-
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
         void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 7916d81..26004a8 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,7 +20,7 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.display.BrightnessSynchronizer;
 
 /**
  * A custom animator that progressively updates a property value at
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
new file mode 100644
index 0000000..18f39e6
--- /dev/null
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -0,0 +1,154 @@
+/*
+ * 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.display.layout;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.DisplayAddress;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds a collection of {@link Display}s. A single instance of this class describes
+ * how to organize one or more DisplayDevices into LogicalDisplays for a particular device
+ * state. For example, there may be one instance of this class to describe display layout when
+ * a foldable device is folded, and a second instance for when the device is unfolded.
+ */
+public class Layout {
+    private static final String TAG = "Layout";
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
+    private final List<Display> mDisplays = new ArrayList<>(2);
+
+    /**
+     *  @return The default display ID, or a new unique one to use.
+     */
+    public static int assignDisplayIdLocked(boolean isDefault) {
+        return isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+    }
+
+    @Override
+    public String toString() {
+        return mDisplays.toString();
+    }
+
+    /**
+     * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
+     *
+     * @param address Address of the device.
+     * @param isDefault Indicates if the device is meant to be the default display.
+     * @return The new layout.
+     */
+    public Display createDisplayLocked(
+            @NonNull DisplayAddress address, boolean isDefault) {
+        if (contains(address)) {
+            Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
+            return null;
+        }
+
+        // See if we're dealing with the "default" display
+        if (isDefault && getById(DEFAULT_DISPLAY) != null) {
+            Slog.w(TAG, "Ignoring attempt to add a second default display: " + address);
+            isDefault = false;
+        }
+
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
+        final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+        final Display layout = new Display(address, logicalDisplayId);
+
+        mDisplays.add(layout);
+        return layout;
+    }
+
+    /**
+     * @param address The address to check.
+     *
+     * @return True if the specified address is used in this layout.
+     */
+    public boolean contains(@NonNull DisplayAddress address) {
+        final int size = mDisplays.size();
+        for (int i = 0; i < size; i++) {
+            if (address.equals(mDisplays.get(i).getAddress())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param id The display ID to check.
+     *
+     * @return The display corresponding to the specified display ID.
+     */
+    public Display getById(int id) {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display layout = mDisplays.get(i);
+            if (id == layout.getLogicalDisplayId()) {
+                return layout;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param index The index of the display to return.
+     *
+     * @return the display at the specified index.
+     */
+    public Display getAt(int index) {
+        return mDisplays.get(index);
+    }
+
+    /**
+     * @return The number of displays defined for this layout.
+     */
+    public int size() {
+        return mDisplays.size();
+    }
+
+    /**
+     * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
+     */
+    public static class Display {
+        private final DisplayAddress mAddress;
+        private final int mLogicalDisplayId;
+
+        Display(@NonNull DisplayAddress address, int logicalDisplayId) {
+            mAddress = address;
+            mLogicalDisplayId = logicalDisplayId;
+        }
+
+        @Override
+        public String toString() {
+            return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "}";
+        }
+
+        public DisplayAddress getAddress() {
+            return mAddress;
+        }
+
+        public int getLogicalDisplayId() {
+            return mLogicalDisplayId;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 1b27572..09c0937 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -72,11 +72,11 @@
     }
 
     @Override
-    public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+    public int updateFontFile(@NonNull FontUpdateRequest request, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
         Objects.requireNonNull(request);
         Objects.requireNonNull(request.getFd());
         Objects.requireNonNull(request.getSignature());
-        Preconditions.checkArgumentNonnegative(baseVersion);
         getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
                 "UPDATE_FONTS permission required.");
         try {
@@ -88,6 +88,21 @@
         }
     }
 
+    @Override
+    public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
+        Objects.requireNonNull(requests);
+        getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
+                "UPDATE_FONTS permission required.");
+        try {
+            update(baseVersion, requests);
+            return FontManager.RESULT_SUCCESS;
+        } catch (SystemFontException e) {
+            Slog.e(TAG, "Failed to update font family", e);
+            return e.getErrorCode();
+        }
+    }
+
     /* package */ static class SystemFontException extends AndroidException {
         private final int mErrorCode;
 
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
index 017f11c..d514aab 100644
--- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -17,6 +17,8 @@
 package com.android.server.graphics.fonts;
 
 import android.annotation.NonNull;
+import android.graphics.FontListParser;
+import android.text.FontConfig;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -30,6 +32,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 /* package */ class PersistentSystemFontConfig {
@@ -38,11 +42,13 @@
     private static final String TAG_ROOT = "fontConfig";
     private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
     private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
+    private static final String TAG_FAMILY = "family";
     private static final String ATTR_VALUE = "value";
 
     /* package */ static class Config {
         public long lastModifiedDate;
         public final Set<String> updatedFontDirs = new ArraySet<>();
+        public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>();
     }
 
     /**
@@ -72,6 +78,11 @@
                     case TAG_UPDATED_FONT_DIR:
                         out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
                         break;
+                    case TAG_FAMILY:
+                        // updatableFontMap is not ready here. We get the base file names by passing
+                        // empty fontDir, and resolve font paths later.
+                        out.fontFamilies.add(FontListParser.readFamily(
+                                parser, "" /* fontDir */, null /* updatableFontMap */));
                     default:
                         Slog.w(TAG, "Skipping unknown tag: " + tag);
                 }
@@ -97,6 +108,13 @@
             out.attribute(null, ATTR_VALUE, dir);
             out.endTag(null, TAG_UPDATED_FONT_DIR);
         }
+        List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+        for (int i = 0; i < fontFamilies.size(); i++) {
+            FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+            out.startTag(null, TAG_FAMILY);
+            FontListParser.writeFamily(out, fontFamily);
+            out.endTag(null, TAG_FAMILY);
+        }
         out.endTag(null, TAG_ROOT);
 
         out.endDocument();
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index dac94f6..08ddc6d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -31,6 +31,8 @@
 import android.util.Base64;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
@@ -40,8 +42,10 @@
 import java.io.IOException;
 import java.security.SecureRandom;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Manages set of updatable font files.
@@ -118,6 +122,12 @@
      */
     private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
 
+    /**
+     * A mutable map containing mapping from font family name to {@link FontConfig.FontFamily}.
+     * The FontFamily entries only reference font files in {@link #mFontFileInfoMap}.
+     */
+    private final ArrayMap<String, FontConfig.FontFamily> mFontFamilyMap = new ArrayMap<>();
+
     UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
             FsverityUtil fsverityUtil) {
         this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
@@ -136,6 +146,7 @@
 
     /* package */ void loadFontFileMap() {
         mFontFileInfoMap.clear();
+        mFontFamilyMap.clear();
         mLastModifiedDate = 0;
         boolean success = false;
         try {
@@ -168,6 +179,17 @@
                 FontFileInfo fontFileInfo = validateFontFile(files[0]);
                 addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
             }
+            // Resolve font file paths.
+            List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
+            for (int i = 0; i < fontFamilies.size(); i++) {
+                FontConfig.FontFamily fontFamily = fontFamilies.get(i);
+                try {
+                    addFontFamily(fontFamily);
+                } catch (SystemFontException e) {
+                    // Ignore failures as updated fonts may be obsoleted by system OTA update.
+                    Slog.i(TAG, "Obsolete font family: " + fontFamily.getName());
+                }
+            }
             success = true;
         } catch (Throwable t) {
             // If something happened during loading system fonts, clear all contents in finally
@@ -177,6 +199,7 @@
             // Delete all files just in case if we find a problematic file.
             if (!success) {
                 mFontFileInfoMap.clear();
+                mFontFamilyMap.clear();
                 mLastModifiedDate = 0;
                 FileUtils.deleteContents(mFilesDir);
             }
@@ -186,10 +209,11 @@
     /* package */ void clearUpdates() throws SystemFontException {
         mFontFileInfoMap.clear();
         FileUtils.deleteContents(mFilesDir);
+        mFontFamilyMap.clear();
 
         mLastModifiedDate = Instant.now().getEpochSecond();
         try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
-            PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+            PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
         } catch (Exception e) {
             throw new SystemFontException(
                     FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -206,17 +230,26 @@
     public void update(List<FontUpdateRequest> requests) throws SystemFontException {
         // Backup the mapping for rollback.
         ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
+        ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap);
         long backupLastModifiedDate = mLastModifiedDate;
         boolean success = false;
         try {
             for (FontUpdateRequest request : requests) {
-                installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
+                switch (request.getType()) {
+                    case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
+                        installFontFile(
+                                request.getFd().getFileDescriptor(), request.getSignature());
+                        break;
+                    case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
+                        addFontFamily(request.getFontFamily());
+                        break;
+                }
             }
 
             // Write config file.
             mLastModifiedDate = Instant.now().getEpochSecond();
             try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
-                PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+                PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
             } catch (Exception e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
@@ -234,6 +267,8 @@
             if (!success) {
                 mFontFileInfoMap.clear();
                 mFontFileInfoMap.putAll(backupMap);
+                mFontFamilyMap.clear();
+                mFontFamilyMap.putAll(backupFamilies);
                 mLastModifiedDate = backupLastModifiedDate;
             }
         }
@@ -454,12 +489,49 @@
         }
     }
 
-    private PersistentSystemFontConfig.Config getPersistentConfig() {
+    /**
+     * Adds a font family to {@link #mFontFamilyMap} and returns true on success.
+     *
+     * <p>This method only accepts adding or updating a font family with a name.
+     * This is to prevent bad font family update from removing glyphs from font fallback chains.
+     * Unnamed font families are used as other named font family's fallback fonts to guarantee a
+     * complete glyph coverage.
+     */
+    private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException {
+        Objects.requireNonNull(fontFamily.getName());
+        FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
+        if (resolvedFontFamily == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_NOT_FOUND,
+                    "Required fonts are not available");
+        }
+        mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
+    }
+
+    @Nullable
+    private FontConfig.FontFamily resolveFontFiles(FontConfig.FontFamily fontFamily) {
+        List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontFamily.getFontList().size());
+        List<FontConfig.Font> fontList = fontFamily.getFontList();
+        for (int i = 0; i < fontList.size(); i++) {
+            FontConfig.Font font = fontList.get(i);
+            FontFileInfo info = mFontFileInfoMap.get(font.getFile().getName());
+            if (info == null) {
+                return null;
+            }
+            resolvedFonts.add(new FontConfig.Font(info.mFile, null, font.getStyle(),
+                    font.getTtcIndex(), font.getFontVariationSettings(), font.getFontFamilyName()));
+        }
+        return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+                fontFamily.getLocaleList(), fontFamily.getVariant());
+    }
+
+    private PersistentSystemFontConfig.Config createPersistentConfig() {
         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
         config.lastModifiedDate = mLastModifiedDate;
         for (FontFileInfo info : mFontFileInfoMap.values()) {
             config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
         }
+        config.fontFamilies.addAll(mFontFamilyMap.values());
         return config;
     }
 
@@ -471,8 +543,24 @@
         return map;
     }
 
+    @VisibleForTesting
+    Map<String, FontConfig.FontFamily> getFontFamilyMap() {
+        return mFontFamilyMap;
+    }
+
     /* package */ FontConfig getSystemFontConfig() {
-        return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
+        FontConfig config = SystemFonts.getSystemFontConfig(getFontFileMap(), 0, 0);
+        List<FontConfig.FontFamily> mergedFamilies =
+                new ArrayList<>(config.getFontFamilies().size() + mFontFamilyMap.size());
+        // We should keep the first font family (config.getFontFamilies().get(0)) because it's used
+        // as a fallback font. See SystemFonts.java.
+        mergedFamilies.addAll(config.getFontFamilies());
+        // When building Typeface, a latter font family definition will override the previous font
+        // family definition with the same name. An exception is config.getFontFamilies.get(0),
+        // which will be used as a fallback font without being overridden.
+        mergedFamilies.addAll(mFontFamilyMap.values());
+        return new FontConfig(
+                mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion);
     }
 
     /* package */ int getConfigVersion() {
diff --git a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
index 1a0a639..7b646b3 100644
--- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
@@ -17,6 +17,7 @@
  */
 
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.DisplayStatusCallback;
 import android.hardware.hdmi.IHdmiControlCallback;
@@ -65,6 +66,20 @@
 
     @Override
     boolean start() {
+        HdmiControlService service = localDevice().mService;
+        if (service.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            HdmiDeviceInfo deviceInfo = service.getHdmiCecNetwork().getCecDeviceInfo(
+                    mTargetAddress);
+            if (deviceInfo != null
+                    && deviceInfo.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                int powerStatus = deviceInfo.getDevicePowerStatus();
+                if (powerStatus != HdmiControlManager.POWER_STATUS_UNKNOWN) {
+                    invokeCallback(powerStatus);
+                    finish();
+                    return true;
+                }
+            }
+        }
         queryDevicePowerStatus();
         mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index cb47bb2..86a8e36 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -103,10 +103,11 @@
         if (mIsCec20) {
             sendSetStreamPath();
         }
-        if (!mIsCec20 || mTarget.getDevicePowerStatus()
-                              == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+        int targetPowerStatus = localDevice().mService.getHdmiCecNetwork()
+                .getCecDeviceInfo(getTargetAddress()).getDevicePowerStatus();
+        if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
             queryDevicePowerStatus();
-        } else if (mTarget.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON) {
+        } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
             invokeCallbackAndFinish(HdmiControlManager.RESULT_SUCCESS);
             return true;
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index 6918064..f64efe7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -411,6 +411,12 @@
             case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
                 notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
                 break;
+            case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY);
+                break;
+            case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
+                notifySettingChanged(HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
+                break;
         }
     }
 
@@ -447,6 +453,8 @@
                 Global.HDMI_CEC_VERSION,
                 Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
+                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
         };
         for (String setting: settings) {
             resolver.registerContentObserver(Global.getUriFor(setting), false,
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 382f0f9..d8914b3 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -654,7 +654,9 @@
                     FOLLOWER_SAFETY_TIMEOUT);
             return true;
         }
-        return false;
+
+        mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
+        return true;
     }
 
     @ServiceThreadOnly
@@ -666,9 +668,8 @@
             final long upTime = SystemClock.uptimeMillis();
             injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
             mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
-            return true;
         }
-        return false;
+        return true;
     }
 
     static void injectKeyEvent(long time, int action, int keycode, int repeat) {
@@ -788,10 +789,7 @@
     }
 
     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
-        // The default behavior of <Record TV Screen> is replying <Feature Abort> with
-        // "Cannot provide source".
-        mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
-        return true;
+        return false;
     }
 
     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 75b52f9..2995252 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -239,6 +239,7 @@
     @ServiceThreadOnly
     protected void onActiveSourceLost() {
         assertRunOnServiceThread();
+        mService.pauseActiveMediaSessions();
         switch (mService.getHdmiCecConfig().getStringValue(
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index a3e18d1..8d6bcad 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1079,7 +1079,10 @@
                         message.getSource(),
                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
             }
-            return super.handleRecordTvScreen(message);
+            // The default behavior of <Record TV Screen> is replying <Feature Abort> with
+            // "Cannot provide source".
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
+            return true;
         }
 
         int recorderAddress = message.getSource();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bdf92ca..fa1fb48 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -56,6 +56,8 @@
 import android.hardware.tv.cec.V1_0.OptionKey;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputManager.TvInputCallback;
 import android.net.Uri;
@@ -3294,6 +3296,16 @@
         }
     }
 
+    @VisibleForTesting
+    void pauseActiveMediaSessions() {
+        MediaSessionManager mediaSessionManager = getContext()
+                .getSystemService(MediaSessionManager.class);
+        List<MediaController> mediaControllers = mediaSessionManager.getActiveSessions(null);
+        for (MediaController mediaController : mediaControllers) {
+            mediaController.getTransportControls().pause();
+        }
+    }
+
     void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
         synchronized (mLock) {
             mActiveSource.logicalAddress = logicalAddress;
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 909fcda..66fc0d9 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_UNKNOWN;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.util.SparseIntArray;
@@ -108,7 +109,10 @@
     private void resetPowerStatus(List<HdmiDeviceInfo> deviceInfos) {
         mPowerStatus.clear();
         for (HdmiDeviceInfo info : deviceInfos) {
-            mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                mPowerStatus.append(info.getLogicalAddress(), info.getDevicePowerStatus());
+            }
         }
     }
 
@@ -117,19 +121,22 @@
                 localDevice().mService.getHdmiCecNetwork().getDeviceInfoList(false);
         resetPowerStatus(deviceInfos);
         for (HdmiDeviceInfo info : deviceInfos) {
-            final int logicalAddress = info.getLogicalAddress();
-            sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
-                    logicalAddress),
-                    new SendMessageCallback() {
-                        @Override
-                        public void onSendCompleted(int error) {
-                            // If fails to send <Give Device Power Status>,
-                            // update power status into UNKNOWN.
-                            if (error != SendMessageResult.SUCCESS) {
-                               updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+            if (localDevice().mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0
+                    || info.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+                final int logicalAddress = info.getLogicalAddress();
+                sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
+                        logicalAddress),
+                        new SendMessageCallback() {
+                            @Override
+                            public void onSendCompleted(int error) {
+                                // If fails to send <Give Device Power Status>,
+                                // update power status into UNKNOWN.
+                                if (error != SendMessageResult.SUCCESS) {
+                                    updatePowerStatus(logicalAddress, POWER_STATUS_UNKNOWN, true);
+                                }
                             }
-                        }
-                    });
+                        });
+            }
         }
 
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a4c8b7..0754df0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -178,6 +178,7 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
@@ -5229,15 +5230,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        boolean asProto = false;
-        for (int argIndex = 0; argIndex < args.length; argIndex++) {
-            if (args[argIndex].equals(PROTO_ARG)) {
-                asProto = true;
-                break;
-            }
-        }
-
-        if (asProto) {
+        if (ArrayUtils.contains(args, PROTO_ARG)) {
             final ImeTracing imeTracing = ImeTracing.getInstance();
             if (imeTracing.isEnabled()) {
                 imeTracing.stopTrace(null, false /* writeToFile */);
@@ -5246,12 +5239,7 @@
                     imeTracing.startTrace(null);
                 });
             }
-        }
-        doDump(fd, pw, args, asProto);
-    }
 
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
-        if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
             proto.flush();
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 43c965d..42b0add 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -36,9 +36,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index ea759bf..5eec315 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -78,7 +78,9 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
+import android.provider.Settings;
 import android.stats.location.LocationStatsEnums;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 
@@ -87,6 +89,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.geofence.GeofenceManager;
 import com.android.server.location.geofence.GeofenceProxy;
 import com.android.server.location.gnss.GnssConfiguration;
@@ -95,10 +98,11 @@
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.AppOpsHelper;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
 import com.android.server.location.injector.EmergencyHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationUsageLogger;
@@ -107,6 +111,8 @@
 import com.android.server.location.injector.SystemAlarmHelper;
 import com.android.server.location.injector.SystemAppForegroundHelper;
 import com.android.server.location.injector.SystemAppOpsHelper;
+import com.android.server.location.injector.SystemDeviceIdleHelper;
+import com.android.server.location.injector.SystemDeviceStationaryHelper;
 import com.android.server.location.injector.SystemEmergencyHelper;
 import com.android.server.location.injector.SystemLocationPermissionsHelper;
 import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
@@ -119,6 +125,7 @@
 import com.android.server.location.provider.MockLocationProvider;
 import com.android.server.location.provider.PassiveLocationProvider;
 import com.android.server.location.provider.PassiveLocationProviderManager;
+import com.android.server.location.provider.StationaryThrottlingLocationProvider;
 import com.android.server.location.provider.proxy.ProxyLocationProvider;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 
@@ -147,9 +154,10 @@
 
         public Lifecycle(Context context) {
             super(context);
+            LocationEventLog eventLog = new LocationEventLog();
             mUserInfoHelper = new LifecycleUserInfoHelper(context);
-            mSystemInjector = new SystemInjector(context, mUserInfoHelper);
-            mService = new LocationManagerService(context, mSystemInjector);
+            mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog);
+            mService = new LocationManagerService(context, mSystemInjector, eventLog);
         }
 
         @Override
@@ -159,7 +167,7 @@
             // client caching behavior is only enabled after seeing the first invalidate
             LocationManager.invalidateLocalLocationEnabledCaches();
             // disable caching for our own process
-            Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class))
+            Objects.requireNonNull(getContext().getSystemService(LocationManager.class))
                     .disableLocalLocationEnabledCaches();
         }
 
@@ -221,6 +229,7 @@
 
     private final Context mContext;
     private final Injector mInjector;
+    private final LocationEventLog mEventLog;
     private final LocalService mLocalService;
 
     private final GeofenceManager mGeofenceManager;
@@ -245,10 +254,10 @@
     private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
             new CopyOnWriteArrayList<>();
 
-    LocationManagerService(Context context, Injector injector) {
+    LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) {
         mContext = context.createAttributionContext(ATTRIBUTION_TAG);
         mInjector = injector;
-
+        mEventLog = eventLog;
         mLocalService = new LocalService();
         LocalServices.addService(LocationManagerInternal.class, mLocalService);
 
@@ -256,7 +265,7 @@
 
         // set up passive provider first since it will be required for all other location providers,
         // which are loaded later once the system is ready.
-        mPassiveManager = new PassiveLocationProviderManager(mContext, injector);
+        mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog);
         addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext));
 
         // TODO: load the gps provider here as well, which will require refactoring
@@ -297,7 +306,7 @@
             }
 
             LocationProviderManager manager = new LocationProviderManager(mContext, mInjector,
-                    providerName, mPassiveManager);
+                    mEventLog, providerName, mPassiveManager);
             addLocationProviderManager(manager, null);
             return manager;
         }
@@ -310,6 +319,18 @@
 
             manager.startManager();
             if (realProvider != null) {
+
+                // custom logic wrapping all non-passive providers
+                if (manager != mPassiveManager) {
+                    boolean enableStationaryThrottling = Settings.Global.getInt(
+                            mContext.getContentResolver(),
+                            Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, 1) != 0;
+                    if (enableStationaryThrottling) {
+                        realProvider = new StationaryThrottlingLocationProvider(manager.getName(),
+                                mInjector, realProvider, mEventLog);
+                    }
+                }
+
                 manager.setRealProvider(realProvider);
             }
             mProviderManagers.add(manager);
@@ -341,7 +362,7 @@
                 com.android.internal.R.string.config_networkLocationProviderPackageName);
         if (networkProvider != null) {
             LocationProviderManager networkManager = new LocationProviderManager(mContext,
-                    mInjector, NETWORK_PROVIDER, mPassiveManager);
+                    mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager);
             addLocationProviderManager(networkManager, networkProvider);
         } else {
             Log.w(TAG, "no network location provider found");
@@ -360,7 +381,7 @@
                 com.android.internal.R.string.config_fusedLocationProviderPackageName);
         if (fusedProvider != null) {
             LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector,
-                    FUSED_PROVIDER, mPassiveManager);
+                    mEventLog, FUSED_PROVIDER, mPassiveManager);
             addLocationProviderManager(fusedManager, fusedProvider);
         } else {
             Log.wtf(TAG, "no fused location provider found");
@@ -375,7 +396,7 @@
             mGnssManagerService.onSystemReady();
 
             LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
-                    GPS_PROVIDER, mPassiveManager);
+                    mEventLog, GPS_PROVIDER, mPassiveManager);
             addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
         }
 
@@ -431,7 +452,7 @@
             Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
         }
 
-        mInjector.getLocationEventLog().logLocationEnabled(userId, enabled);
+        mEventLog.logLocationEnabled(userId, enabled);
 
         Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
                 .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
@@ -1193,9 +1214,27 @@
 
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
-        if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
-            mGnssManagerService.dump(fd, ipw, args);
-            return;
+        if (args.length > 0) {
+            LocationProviderManager manager = getLocationProviderManager(args[0]);
+            if (manager != null) {
+                ipw.println("Provider:");
+                ipw.increaseIndent();
+                manager.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+
+                ipw.println("Event Log:");
+                ipw.increaseIndent();
+                mEventLog.iterate(manager.getName(), ipw::println);
+                ipw.decreaseIndent();
+                return;
+            }
+
+            if ("--gnssmetrics".equals(args[0])) {
+                if (mGnssManagerService != null) {
+                    mGnssManagerService.dump(fd, ipw, args);
+                }
+                return;
+            }
         }
 
         ipw.println("Location Manager State:");
@@ -1227,6 +1266,26 @@
         }
         ipw.decreaseIndent();
 
+        ipw.println("Historical Aggregate Location Provider Data:");
+        ipw.increaseIndent();
+        ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats =
+                mEventLog.copyAggregateStats();
+        for (int i = 0; i < aggregateStats.size(); i++) {
+            ipw.print(aggregateStats.keyAt(i));
+            ipw.println(":");
+            ipw.increaseIndent();
+            ArrayMap<String, LocationEventLog.AggregateStats> providerStats =
+                    aggregateStats.valueAt(i);
+            for (int j = 0; j < providerStats.size(); j++) {
+                ipw.print(providerStats.keyAt(j));
+                ipw.print(": ");
+                providerStats.valueAt(j).updateTotals();
+                ipw.println(providerStats.valueAt(j));
+            }
+            ipw.decreaseIndent();
+        }
+        ipw.decreaseIndent();
+
         if (mGnssManagerService != null) {
             ipw.println("GNSS Manager:");
             ipw.increaseIndent();
@@ -1241,7 +1300,7 @@
 
         ipw.println("Event Log:");
         ipw.increaseIndent();
-        mInjector.getLocationEventLog().iterate(ipw::println);
+        mEventLog.iterate(ipw::println);
         ipw.decreaseIndent();
     }
 
@@ -1320,7 +1379,6 @@
         private final Context mContext;
 
         private final UserInfoHelper mUserInfoHelper;
-        private final LocationEventLog mLocationEventLog;
         private final AlarmHelper mAlarmHelper;
         private final SystemAppOpsHelper mAppOpsHelper;
         private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
@@ -1328,6 +1386,8 @@
         private final SystemAppForegroundHelper mAppForegroundHelper;
         private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
         private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
+        private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
+        private final SystemDeviceIdleHelper mDeviceIdleHelper;
         private final LocationAttributionHelper mLocationAttributionHelper;
         private final LocationUsageLogger mLocationUsageLogger;
 
@@ -1339,20 +1399,20 @@
         @GuardedBy("this")
         private boolean mSystemReady;
 
-        SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+        SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) {
             mContext = context;
 
             mUserInfoHelper = userInfoHelper;
-            mLocationEventLog = new LocationEventLog();
             mAlarmHelper = new SystemAlarmHelper(context);
             mAppOpsHelper = new SystemAppOpsHelper(context);
             mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
                     mAppOpsHelper);
             mSettingsHelper = new SystemSettingsHelper(context);
             mAppForegroundHelper = new SystemAppForegroundHelper(context);
-            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context,
-                    mLocationEventLog);
+            mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog);
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
+            mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
+            mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
             mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
             mLocationUsageLogger = new LocationUsageLogger();
         }
@@ -1413,6 +1473,16 @@
         }
 
         @Override
+        public DeviceStationaryHelper getDeviceStationaryHelper() {
+            return mDeviceStationaryHelper;
+        }
+
+        @Override
+        public DeviceIdleHelper getDeviceIdleHelper() {
+            return mDeviceIdleHelper;
+        }
+
+        @Override
         public LocationAttributionHelper getLocationAttributionHelper() {
             return mLocationAttributionHelper;
         }
@@ -1430,11 +1500,6 @@
         }
 
         @Override
-        public LocationEventLog getLocationEventLog() {
-            return mLocationEventLog;
-        }
-
-        @Override
         public LocationUsageLogger getLocationUsageLogger() {
             return mLocationUsageLogger;
         }
diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
index b5746bb..12dd3e6 100644
--- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
@@ -16,12 +16,13 @@
 
 package com.android.server.location.eventlog;
 
+import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.util.TimeUtils;
 
 import com.android.internal.util.Preconditions;
 
-import java.util.ListIterator;
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.function.Consumer;
 
@@ -35,6 +36,7 @@
         boolean isFiller();
         long getTimeDeltaMs();
         String getLogString();
+        boolean filter(@Nullable String filter);
     }
 
     private static final class FillerEvent implements Log {
@@ -62,6 +64,11 @@
         public String getLogString() {
             throw new AssertionError();
         }
+
+        @Override
+        public boolean filter(String filter) {
+            return false;
+        }
     }
 
     /**
@@ -87,6 +94,11 @@
         public final long getTimeDeltaMs() {
             return Integer.toUnsignedLong(mTimeDelta);
         }
+
+        @Override
+        public boolean filter(String filter) {
+            return false;
+        }
     }
 
     // circular buffer of log entries
@@ -198,6 +210,17 @@
         }
     }
 
+    /**
+     * Iterates over the event log, passing each filter-matching log string to the given
+     * consumer.
+     */
+    public synchronized void iterate(String filter, Consumer<String> consumer) {
+        LogIterator it = new LogIterator(filter);
+        while (it.hasNext()) {
+            consumer.accept(it.next());
+        }
+    }
+
     // returns the index of the first element
     private int startIndex() {
         return wrapIndex(mLogEndIndex - mLogSize);
@@ -205,12 +228,13 @@
 
     // returns the index after this one
     private int incrementIndex(int index) {
-        return wrapIndex(index + 1);
-    }
-
-    // returns the index before this one
-    private int decrementIndex(int index) {
-        return wrapIndex(index - 1);
+        if (index == -1) {
+            return startIndex();
+        } else if (index >= 0) {
+            return wrapIndex(index + 1);
+        } else {
+            throw new IllegalArgumentException();
+        }
     }
 
     // rolls over the given index if necessary
@@ -219,7 +243,9 @@
         return (index % mLog.length + mLog.length) % mLog.length;
     }
 
-    private class LogIterator implements ListIterator<String> {
+    private class LogIterator implements Iterator<String> {
+
+        private final @Nullable String mFilter;
 
         private final long mSystemTimeDeltaMs;
 
@@ -228,10 +254,17 @@
         private int mCount;
 
         LogIterator() {
+            this(null);
+        }
+
+        LogIterator(@Nullable String filter) {
+            mFilter = filter;
             mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
             mCurrentRealtimeMs = mStartRealtimeMs;
-            mIndex = startIndex();
-            mCount = 0;
+            mIndex = -1;
+            mCount = -1;
+
+            increment();
         }
 
         @Override
@@ -239,75 +272,17 @@
             return mCount < mLogSize;
         }
 
-        @Override
-        public boolean hasPrevious() {
-            return mCount > 0;
-        }
-
-        @Override
-        // return then increment
         public String next() {
             if (!hasNext()) {
                 throw new NoSuchElementException();
             }
 
             Log log = mLog[mIndex];
-            long nextDeltaMs = log.getTimeDeltaMs();
-            long realtimeMs = mCurrentRealtimeMs + nextDeltaMs;
+            long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs;
 
-            // calculate next index, skipping filler events
-            do {
-                mCurrentRealtimeMs += nextDeltaMs;
-                mIndex = incrementIndex(mIndex);
-                if (++mCount < mLogSize) {
-                    nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
-                }
-            } while (mCount < mLogSize && mLog[mIndex].isFiller());
+            increment();
 
-            return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
-        }
-
-        @Override
-        // decrement then return
-        public String previous() {
-            Log log;
-            long currentDeltaMs;
-            long realtimeMs;
-
-            // calculate previous index, skipping filler events with MAX_TIME_DELTA
-            do {
-                if (!hasPrevious()) {
-                    throw new NoSuchElementException();
-                }
-
-                mIndex = decrementIndex(mIndex);
-                mCount--;
-
-                log = mLog[mIndex];
-                realtimeMs = mCurrentRealtimeMs;
-
-                if (mCount > 0) {
-                    currentDeltaMs = log.getTimeDeltaMs();
-                    mCurrentRealtimeMs -= currentDeltaMs;
-                }
-            } while (mCount >= 0 && log.isFiller());
-
-            return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
-        }
-
-        @Override
-        public int nextIndex() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int previousIndex() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void add(String s) {
-            throw new UnsupportedOperationException();
+            return getTimePrefix(timeMs) + log.getLogString();
         }
 
         @Override
@@ -315,9 +290,16 @@
             throw new UnsupportedOperationException();
         }
 
-        @Override
-        public void set(String s) {
-            throw new UnsupportedOperationException();
+        private void increment() {
+            long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs();
+            do {
+                mCurrentRealtimeMs += nextDeltaMs;
+                mIndex = incrementIndex(mIndex);
+                if (++mCount < mLogSize) {
+                    nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
+                }
+            } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null
+                    && !mLog[mIndex].filter(mFilter))));
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
new file mode 100644
index 0000000..dbfd0a5
--- /dev/null
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -0,0 +1,536 @@
+/*
+ * 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.location.eventlog;
+
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.util.TimeUtils.formatDuration;
+
+import static com.android.server.location.LocationManagerService.D;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.location.LocationRequest;
+import android.location.provider.ProviderRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Build;
+import android.os.PowerManager.LocationPowerSaveMode;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+/** In memory event log for location events. */
+public class LocationEventLog extends LocalEventLog {
+
+    private static int getLogSize() {
+        if (Build.IS_DEBUGGABLE || D) {
+            return 500;
+        } else {
+            return 200;
+        }
+    }
+
+    private static final int EVENT_LOCATION_ENABLED = 1;
+    private static final int EVENT_PROVIDER_ENABLED = 2;
+    private static final int EVENT_PROVIDER_MOCKED = 3;
+    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
+    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
+    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
+    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
+    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
+    private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 9;
+    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 10;
+
+    @GuardedBy("mAggregateStats")
+    private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats;
+
+    public LocationEventLog() {
+        super(getLogSize());
+        mAggregateStats = new ArrayMap<>(4);
+    }
+
+    public ArrayMap<String, ArrayMap<String, AggregateStats>> copyAggregateStats() {
+        synchronized (mAggregateStats) {
+            ArrayMap<String, ArrayMap<String, AggregateStats>> copy = new ArrayMap<>(
+                    mAggregateStats);
+            for (int i = 0; i < copy.size(); i++) {
+                copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i)));
+            }
+            return copy;
+        }
+    }
+
+    private AggregateStats getAggregateStats(String provider, String packageName) {
+        synchronized (mAggregateStats) {
+            ArrayMap<String, AggregateStats> packageMap = mAggregateStats.get(provider);
+            if (packageMap == null) {
+                packageMap = new ArrayMap<>(2);
+                mAggregateStats.put(provider, packageMap);
+            }
+            AggregateStats stats = packageMap.get(packageName);
+            if (stats == null) {
+                stats = new AggregateStats();
+                packageMap.put(packageName, stats);
+            }
+            return stats;
+        }
+    }
+
+    /** Logs a location enabled/disabled event. */
+    public void logLocationEnabled(int userId, boolean enabled) {
+        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+    }
+
+    /** Logs a location provider enabled/disabled event. */
+    public void logProviderEnabled(String provider, int userId, boolean enabled) {
+        addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
+    }
+
+    /** Logs a location provider being replaced/unreplaced by a mock provider. */
+    public void logProviderMocked(String provider, boolean mocked) {
+        addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
+    }
+
+    /** Logs a new client registration for a location provider. */
+    public void logProviderClientRegistered(String provider, CallerIdentity identity,
+            LocationRequest request) {
+        addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
+        getAggregateStats(provider, identity.getPackageName())
+                .markRequestAdded(request.getIntervalMillis());
+    }
+
+    /** Logs a client unregistration for a location provider. */
+    public void logProviderClientUnregistered(String provider, CallerIdentity identity) {
+        addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
+        getAggregateStats(provider, identity.getPackageName()).markRequestRemoved();
+    }
+
+    /** Logs a client for a location provider entering the active state. */
+    public void logProviderClientActive(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestActive();
+    }
+
+    /** Logs a client for a location provider leaving the active state. */
+    public void logProviderClientInactive(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestInactive();
+    }
+
+    /** Logs a client for a location provider entering the foreground state. */
+    public void logProviderClientForeground(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestForeground();
+    }
+
+    /** Logs a client for a location provider leaving the foreground state. */
+    public void logProviderClientBackground(String provider, CallerIdentity identity) {
+        getAggregateStats(provider, identity.getPackageName()).markRequestBackground();
+    }
+
+    /** Logs a change to the provider request for a location provider. */
+    public void logProviderUpdateRequest(String provider, ProviderRequest request) {
+        addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
+    }
+
+    /** Logs a new incoming location for a location provider. */
+    public void logProviderReceivedLocations(String provider, int numLocations) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
+        }
+    }
+
+    /** Logs a location deliver for a client of a location provider. */
+    public void logProviderDeliveredLocations(String provider, int numLocations,
+            CallerIdentity identity) {
+        if (Build.IS_DEBUGGABLE || D) {
+            addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
+        }
+        getAggregateStats(provider, identity.getPackageName()).markLocationDelivered();
+    }
+
+    /** Logs that a provider has entered or exited stationary throttling. */
+    public void logProviderStationaryThrottled(String provider, boolean throttled) {
+        addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+    }
+
+    /** Logs that the location power save mode has changed. */
+    public void logLocationPowerSaveMode(
+            @LocationPowerSaveMode int locationPowerSaveMode) {
+        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
+    }
+
+    @Override
+    protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
+        switch (event) {
+            case EVENT_LOCATION_ENABLED:
+                return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+            case EVENT_PROVIDER_ENABLED:
+                return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
+                        (Boolean) args[2]);
+            case EVENT_PROVIDER_MOCKED:
+                return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
+            case EVENT_PROVIDER_REGISTER_CLIENT:
+                return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
+                        (CallerIdentity) args[1], (LocationRequest) args[2]);
+            case EVENT_PROVIDER_UNREGISTER_CLIENT:
+                return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
+                        (CallerIdentity) args[1], null);
+            case EVENT_PROVIDER_UPDATE_REQUEST:
+                return new ProviderUpdateEvent(timeDelta, (String) args[0],
+                        (ProviderRequest) args[1]);
+            case EVENT_PROVIDER_RECEIVE_LOCATION:
+                return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
+                        (Integer) args[1]);
+            case EVENT_PROVIDER_DELIVER_LOCATION:
+                return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
+                        (Integer) args[1], (CallerIdentity) args[2]);
+            case EVENT_PROVIDER_STATIONARY_THROTTLED:
+                return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
+                        (Boolean) args[1]);
+            case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
+                return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    private abstract static class ProviderEvent extends LogEvent {
+
+        protected final String mProvider;
+
+        protected ProviderEvent(long timeDelta, String provider) {
+            super(timeDelta);
+            mProvider = provider;
+        }
+
+        @Override
+        public boolean filter(String filter) {
+            return mProvider.equals(filter);
+        }
+    }
+
+    private static final class ProviderEnabledEvent extends ProviderEvent {
+
+        private final int mUserId;
+        private final boolean mEnabled;
+
+        protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
+                boolean enabled) {
+            super(timeDelta, provider);
+            mUserId = userId;
+            mEnabled = enabled;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
+                    : "disabled");
+        }
+    }
+
+    private static final class ProviderMockedEvent extends ProviderEvent {
+
+        private final boolean mMocked;
+
+        protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
+            super(timeDelta, provider);
+            mMocked = mocked;
+        }
+
+        @Override
+        public String getLogString() {
+            if (mMocked) {
+                return mProvider + " provider added mock provider override";
+            } else {
+                return mProvider + " provider removed mock provider override";
+            }
+        }
+    }
+
+    private static final class ProviderRegisterEvent extends ProviderEvent {
+
+        private final boolean mRegistered;
+        private final CallerIdentity mIdentity;
+        @Nullable private final LocationRequest mLocationRequest;
+
+        private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
+                CallerIdentity identity, @Nullable LocationRequest locationRequest) {
+            super(timeDelta, provider);
+            mRegistered = registered;
+            mIdentity = identity;
+            mLocationRequest = locationRequest;
+        }
+
+        @Override
+        public String getLogString() {
+            if (mRegistered) {
+                return mProvider + " provider " + "+registration " + mIdentity + " -> "
+                        + mLocationRequest;
+            } else {
+                return mProvider + " provider " + "-registration " + mIdentity;
+            }
+        }
+    }
+
+    private static final class ProviderUpdateEvent extends ProviderEvent {
+
+        private final ProviderRequest mRequest;
+
+        private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
+            super(timeDelta, provider);
+            mRequest = request;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider request = " + mRequest;
+        }
+    }
+
+    private static final class ProviderReceiveLocationEvent extends ProviderEvent {
+
+        private final int mNumLocations;
+
+        private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
+            super(timeDelta, provider);
+            mNumLocations = numLocations;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider received location[" + mNumLocations + "]";
+        }
+    }
+
+    private static final class ProviderDeliverLocationEvent extends ProviderEvent {
+
+        private final int mNumLocations;
+        @Nullable private final CallerIdentity mIdentity;
+
+        private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
+                @Nullable CallerIdentity identity) {
+            super(timeDelta, provider);
+            mNumLocations = numLocations;
+            mIdentity = identity;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider delivered location[" + mNumLocations + "] to "
+                    + mIdentity;
+        }
+    }
+
+    private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
+
+        private final boolean mStationaryThrottled;
+
+        private ProviderStationaryThrottledEvent(long timeDelta, String provider,
+                boolean stationaryThrottled) {
+            super(timeDelta, provider);
+            mStationaryThrottled = stationaryThrottled;
+        }
+
+        @Override
+        public String getLogString() {
+            return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
+                    : "unthrottled");
+        }
+    }
+
+    private static final class LocationPowerSaveModeEvent extends LogEvent {
+
+        @LocationPowerSaveMode
+        private final int mLocationPowerSaveMode;
+
+        private LocationPowerSaveModeEvent(long timeDelta,
+                @LocationPowerSaveMode int locationPowerSaveMode) {
+            super(timeDelta);
+            mLocationPowerSaveMode = locationPowerSaveMode;
+        }
+
+        @Override
+        public String getLogString() {
+            String mode;
+            switch (mLocationPowerSaveMode) {
+                case LOCATION_MODE_NO_CHANGE:
+                    mode = "NO_CHANGE";
+                    break;
+                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+                    mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
+                    break;
+                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+                    mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
+                    break;
+                case LOCATION_MODE_FOREGROUND_ONLY:
+                    mode = "FOREGROUND_ONLY";
+                    break;
+                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+                    mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
+                    break;
+                default:
+                    mode = "UNKNOWN";
+                    break;
+            }
+            return "location power save mode changed to " + mode;
+        }
+    }
+
+    private static final class LocationEnabledEvent extends LogEvent {
+
+        private final int mUserId;
+        private final boolean mEnabled;
+
+        private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
+            super(timeDelta);
+            mUserId = userId;
+            mEnabled = enabled;
+        }
+
+        @Override
+        public String getLogString() {
+            return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+        }
+    }
+
+    /**
+     * Aggregate statistics for a single package under a single provider.
+     */
+    public static final class AggregateStats {
+
+        @GuardedBy("this")
+        private int mAddedRequestCount;
+        @GuardedBy("this")
+        private int mActiveRequestCount;
+        @GuardedBy("this")
+        private int mForegroundRequestCount;
+        @GuardedBy("this")
+        private int mDeliveredLocationCount;
+
+        @GuardedBy("this")
+        private long mFastestIntervalMs = Long.MAX_VALUE;
+        @GuardedBy("this")
+        private long mSlowestIntervalMs = 0;
+
+        @GuardedBy("this")
+        private long mAddedTimeTotalMs;
+        @GuardedBy("this")
+        private long mAddedTimeLastUpdateRealtimeMs;
+
+        @GuardedBy("this")
+        private long mActiveTimeTotalMs;
+        @GuardedBy("this")
+        private long mActiveTimeLastUpdateRealtimeMs;
+
+        @GuardedBy("this")
+        private long mForegroundTimeTotalMs;
+        @GuardedBy("this")
+        private long mForegroundTimeLastUpdateRealtimeMs;
+
+        AggregateStats() {}
+
+        synchronized void markRequestAdded(long intervalMillis) {
+            if (mAddedRequestCount++ == 0) {
+                mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+
+            mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+            mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+        }
+
+        synchronized void markRequestRemoved() {
+            updateTotals();
+            --mAddedRequestCount;
+            Preconditions.checkState(mAddedRequestCount >= 0);
+
+            mActiveRequestCount = min(mAddedRequestCount, mActiveRequestCount);
+            mForegroundRequestCount = min(mAddedRequestCount, mForegroundRequestCount);
+        }
+
+        synchronized void markRequestActive() {
+            Preconditions.checkState(mAddedRequestCount > 0);
+            if (mActiveRequestCount++ == 0) {
+                mActiveTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+        }
+
+        synchronized void markRequestInactive() {
+            updateTotals();
+            --mActiveRequestCount;
+            Preconditions.checkState(mActiveRequestCount >= 0);
+        }
+
+        synchronized void markRequestForeground() {
+            Preconditions.checkState(mAddedRequestCount > 0);
+            if (mForegroundRequestCount++ == 0) {
+                mForegroundTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+            }
+        }
+
+        synchronized void markRequestBackground() {
+            updateTotals();
+            --mForegroundRequestCount;
+            Preconditions.checkState(mForegroundRequestCount >= 0);
+        }
+
+        synchronized void markLocationDelivered() {
+            mDeliveredLocationCount++;
+        }
+
+        public synchronized void updateTotals() {
+            if (mAddedRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+                mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+            if (mActiveRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mActiveTimeTotalMs += realtimeMs - mActiveTimeLastUpdateRealtimeMs;
+                mActiveTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+            if (mForegroundRequestCount > 0) {
+                long realtimeMs = SystemClock.elapsedRealtime();
+                mForegroundTimeTotalMs += realtimeMs - mForegroundTimeLastUpdateRealtimeMs;
+                mForegroundTimeLastUpdateRealtimeMs = realtimeMs;
+            }
+        }
+
+        @Override
+        public synchronized String toString() {
+            return "min/max interval = " + intervalToString(mFastestIntervalMs) + "/"
+                    + intervalToString(mSlowestIntervalMs)
+                    + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs)
+                    + "/" + formatDuration(mActiveTimeTotalMs) + "/"
+                    + formatDuration(mForegroundTimeTotalMs) + ", locations = "
+                    + mDeliveredLocationCount;
+        }
+
+        private static String intervalToString(long intervalMs) {
+            if (intervalMs == LocationRequest.PASSIVE_INTERVAL) {
+                return "passive";
+            } else {
+                return MILLISECONDS.toSeconds(intervalMs) + "s";
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
new file mode 100644
index 0000000..e820b00
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceIdleHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.location.injector;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for device idle status.
+ */
+public abstract class DeviceIdleHelper {
+
+    /**
+     * Listener for device stationary status.
+     */
+    public interface DeviceIdleListener {
+        /**
+         * Called when device idle state has changed.
+         */
+        void onDeviceIdleChanged(boolean deviceIdle);
+    }
+
+    private final CopyOnWriteArrayList<DeviceIdleListener> mListeners;
+
+    protected DeviceIdleHelper() {
+        mListeners = new CopyOnWriteArrayList<>();
+    }
+
+    /**
+     * Adds a listener for device idle status.
+     */
+    public final synchronized void addListener(DeviceIdleListener listener) {
+        if (mListeners.add(listener) && mListeners.size() == 1) {
+            registerInternal();
+        }
+    }
+
+    /**
+     * Removes a listener for device idle status.
+     */
+    public final synchronized void removeListener(DeviceIdleListener listener) {
+        if (mListeners.remove(listener) && mListeners.isEmpty()) {
+            unregisterInternal();
+        }
+    }
+
+    protected final void notifyDeviceIdleChanged() {
+        boolean deviceIdle = isDeviceIdle();
+
+        for (DeviceIdleListener listener : mListeners) {
+            listener.onDeviceIdleChanged(deviceIdle);
+        }
+    }
+
+    protected abstract void registerInternal();
+
+    protected abstract void unregisterInternal();
+
+    /**
+     * Returns true if the device is currently idle.
+     */
+    public abstract boolean isDeviceIdle();
+}
diff --git a/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
new file mode 100644
index 0000000..b77c0f7
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/DeviceStationaryHelper.java
@@ -0,0 +1,36 @@
+/*
+ * 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.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+/**
+ * Provides accessors and listeners for device stationary status.
+ */
+public abstract class DeviceStationaryHelper {
+
+    /**
+     * Adds a listener for stationary status. The listener will be immediately invoked with the
+     * current stationary status.
+     */
+    public abstract void addListener(DeviceIdleInternal.StationaryListener listener);
+
+    /**
+     * Removes a listener for stationary status.
+     */
+    public abstract void removeListener(DeviceIdleInternal.StationaryListener listener);
+}
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 03938b2..b035118 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -48,6 +48,12 @@
     /** Returns a ScreenInteractiveHelper. */
     ScreenInteractiveHelper getScreenInteractiveHelper();
 
+    /** Returns a DeviceStationaryHelper. */
+    DeviceStationaryHelper getDeviceStationaryHelper();
+
+    /** Returns a DeviceIdleHelper. */
+    DeviceIdleHelper getDeviceIdleHelper();
+
     /** Returns a LocationAttributionHelper. */
     LocationAttributionHelper getLocationAttributionHelper();
 
@@ -56,7 +62,4 @@
 
     /** Returns a LocationUsageLogger. */
     LocationUsageLogger getLocationUsageLogger();
-
-    /** Returns a LocationEventLog. */
-    LocationEventLog getLocationEventLog();
 }
diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java
deleted file mode 100644
index 8d73518..0000000
--- a/services/core/java/com/android/server/location/injector/LocationEventLog.java
+++ /dev/null
@@ -1,324 +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.
- */
-
-package com.android.server.location.injector;
-
-import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
-import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
-import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
-import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
-
-import static com.android.server.location.LocationManagerService.D;
-
-import android.annotation.Nullable;
-import android.location.LocationRequest;
-import android.location.provider.ProviderRequest;
-import android.location.util.identity.CallerIdentity;
-import android.os.Build;
-import android.os.PowerManager.LocationPowerSaveMode;
-
-import com.android.server.location.eventlog.LocalEventLog;
-
-/** In memory event log for location events. */
-public class LocationEventLog extends LocalEventLog {
-
-    private static int getLogSize() {
-        if (Build.IS_DEBUGGABLE || D) {
-            return 500;
-        } else {
-            return 200;
-        }
-    }
-
-    private static final int EVENT_LOCATION_ENABLED = 1;
-    private static final int EVENT_PROVIDER_ENABLED = 2;
-    private static final int EVENT_PROVIDER_MOCKED = 3;
-    private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
-    private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
-    private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
-    private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
-    private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
-    private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
-
-    public LocationEventLog() {
-        super(getLogSize());
-    }
-
-    /** Logs a location enabled/disabled event. */
-    public void logLocationEnabled(int userId, boolean enabled) {
-        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
-    }
-
-    /** Logs a location provider enabled/disabled event. */
-    public void logProviderEnabled(String provider, int userId, boolean enabled) {
-        addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
-    }
-
-    /** Logs a location provider being replaced/unreplaced by a mock provider. */
-    public void logProviderMocked(String provider, boolean mocked) {
-        addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
-    }
-
-    /** Logs a new client registration for a location provider. */
-    public void logProviderClientRegistered(String provider, CallerIdentity identity,
-            LocationRequest request) {
-        addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
-    }
-
-    /** Logs a client unregistration for a location provider. */
-    public void logProviderClientUnregistered(String provider,
-            CallerIdentity identity) {
-        addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
-    }
-
-    /** Logs a change to the provider request for a location provider. */
-    public void logProviderUpdateRequest(String provider, ProviderRequest request) {
-        addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
-    }
-
-    /** Logs a new incoming location for a location provider. */
-    public void logProviderReceivedLocations(String provider, int numLocations) {
-        if (Build.IS_DEBUGGABLE || D) {
-            addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider, numLocations);
-        }
-    }
-
-    /** Logs a location deliver for a client of a location provider. */
-    public void logProviderDeliveredLocations(String provider, int numLocations,
-            CallerIdentity identity) {
-        if (Build.IS_DEBUGGABLE || D) {
-            addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, numLocations, identity);
-        }
-    }
-
-    /** Logs that the location power save mode has changed. */
-    public void logLocationPowerSaveMode(
-            @LocationPowerSaveMode int locationPowerSaveMode) {
-        addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
-    }
-
-    @Override
-    protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
-        switch (event) {
-            case EVENT_LOCATION_ENABLED:
-                return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
-            case EVENT_PROVIDER_ENABLED:
-                return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
-                        (Boolean) args[2]);
-            case EVENT_PROVIDER_MOCKED:
-                return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
-            case EVENT_PROVIDER_REGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
-                        (CallerIdentity) args[1], (LocationRequest) args[2]);
-            case EVENT_PROVIDER_UNREGISTER_CLIENT:
-                return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
-                        (CallerIdentity) args[1], null);
-            case EVENT_PROVIDER_UPDATE_REQUEST:
-                return new ProviderUpdateEvent(timeDelta, (String) args[0],
-                        (ProviderRequest) args[1]);
-            case EVENT_PROVIDER_RECEIVE_LOCATION:
-                return new ProviderReceiveLocationEvent(timeDelta, (String) args[0],
-                        (Integer) args[1]);
-            case EVENT_PROVIDER_DELIVER_LOCATION:
-                return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
-                        (Integer) args[1], (CallerIdentity) args[2]);
-            case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
-                return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
-            default:
-                throw new AssertionError();
-        }
-    }
-
-    private static class ProviderEnabledEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mUserId;
-        private final boolean mEnabled;
-
-        protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
-                boolean enabled) {
-            super(timeDelta);
-            mProvider = provider;
-            mUserId = userId;
-            mEnabled = enabled;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
-                    : "disabled");
-        }
-    }
-
-    private static class ProviderMockedEvent extends LogEvent {
-
-        private final String mProvider;
-        private final boolean mMocked;
-
-        protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
-            super(timeDelta);
-            mProvider = provider;
-            mMocked = mocked;
-        }
-
-        @Override
-        public String getLogString() {
-            if (mMocked) {
-                return mProvider + " provider added mock provider override";
-            } else {
-                return mProvider + " provider removed mock provider override";
-            }
-        }
-    }
-
-    private static class ProviderRegisterEvent extends LogEvent {
-
-        private final String mProvider;
-        private final boolean mRegistered;
-        private final CallerIdentity mIdentity;
-        @Nullable private final LocationRequest mLocationRequest;
-
-        private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
-                CallerIdentity identity, @Nullable LocationRequest locationRequest) {
-            super(timeDelta);
-            mProvider = provider;
-            mRegistered = registered;
-            mIdentity = identity;
-            mLocationRequest = locationRequest;
-        }
-
-        @Override
-        public String getLogString() {
-            if (mRegistered) {
-                return mProvider + " provider " + "+registration " + mIdentity + " -> "
-                        + mLocationRequest;
-            } else {
-                return mProvider + " provider " + "-registration " + mIdentity;
-            }
-        }
-    }
-
-    private static class ProviderUpdateEvent extends LogEvent {
-
-        private final String mProvider;
-        private final ProviderRequest mRequest;
-
-        private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
-            super(timeDelta);
-            mProvider = provider;
-            mRequest = request;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider request = " + mRequest;
-        }
-    }
-
-    private static class ProviderReceiveLocationEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mNumLocations;
-
-        private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) {
-            super(timeDelta);
-            mProvider = provider;
-            mNumLocations = numLocations;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider received location[" + mNumLocations + "]";
-        }
-    }
-
-    private static class ProviderDeliverLocationEvent extends LogEvent {
-
-        private final String mProvider;
-        private final int mNumLocations;
-        @Nullable private final CallerIdentity mIdentity;
-
-        private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations,
-                @Nullable CallerIdentity identity) {
-            super(timeDelta);
-            mProvider = provider;
-            mNumLocations = numLocations;
-            mIdentity = identity;
-        }
-
-        @Override
-        public String getLogString() {
-            return mProvider + " provider delivered location[" + mNumLocations + "] to "
-                    + mIdentity;
-        }
-    }
-
-    private static class LocationPowerSaveModeEvent extends LogEvent {
-
-        @LocationPowerSaveMode
-        private final int mLocationPowerSaveMode;
-
-        private LocationPowerSaveModeEvent(long timeDelta,
-                @LocationPowerSaveMode int locationPowerSaveMode) {
-            super(timeDelta);
-            mLocationPowerSaveMode = locationPowerSaveMode;
-        }
-
-        @Override
-        public String getLogString() {
-            String mode;
-            switch (mLocationPowerSaveMode) {
-                case LOCATION_MODE_NO_CHANGE:
-                    mode = "NO_CHANGE";
-                    break;
-                case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
-                    mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
-                    break;
-                case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
-                    mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
-                    break;
-                case LOCATION_MODE_FOREGROUND_ONLY:
-                    mode = "FOREGROUND_ONLY";
-                    break;
-                case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
-                    mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
-                    break;
-                default:
-                    mode = "UNKNOWN";
-                    break;
-            }
-            return "location power save mode changed to " + mode;
-        }
-    }
-
-    private static class LocationEnabledEvent extends LogEvent {
-
-        private final int mUserId;
-        private final boolean mEnabled;
-
-        private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
-            super(timeDelta);
-            mUserId = userId;
-            mEnabled = enabled;
-        }
-
-        @Override
-        public String getLogString() {
-            return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
index 532826a..cc00d56 100644
--- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java
@@ -24,6 +24,8 @@
 import android.os.PowerManager.LocationPowerSaveMode;
 import android.util.Log;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
new file mode 100644
index 0000000..6a89079
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceIdleHelper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.android.server.location.injector;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+import com.android.server.FgThread;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceIdleHelper extends DeviceIdleHelper {
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+
+    private @Nullable BroadcastReceiver mReceiver;
+
+    public SystemDeviceIdleHelper(Context context) {
+        mContext = context;
+        mPowerManager = context.getSystemService(PowerManager.class);
+    }
+
+    @Override
+    protected void registerInternal() {
+        if (mReceiver == null) {
+            mReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    notifyDeviceIdleChanged();
+                }
+            };
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED), null,
+                    FgThread.getHandler());
+        }
+    }
+
+    @Override
+    protected void unregisterInternal() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    @Override
+    public boolean isDeviceIdle() {
+        return mPowerManager.isDeviceIdleMode();
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
new file mode 100644
index 0000000..6f0e681
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemDeviceStationaryHelper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
+
+import java.util.Objects;
+
+/**
+ * Provides accessors and listeners for device stationary state.
+ */
+public class SystemDeviceStationaryHelper extends DeviceStationaryHelper {
+
+    private final DeviceIdleInternal mDeviceIdle;
+
+    public SystemDeviceStationaryHelper() {
+        mDeviceIdle = Objects.requireNonNull(LocalServices.getService(DeviceIdleInternal.class));
+    }
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.registerStationaryListener(listener);
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        mDeviceIdle.unregisterStationaryListener(listener);
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
index 1b74865..c47a64d 100644
--- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java
@@ -25,6 +25,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 
 import java.util.Objects;
 import java.util.function.Consumer;
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index e22a014..4e0a0b8 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -337,8 +337,10 @@
 
         @Override
         public State setListener(@Nullable Listener listener) {
-            return mInternalState.updateAndGet(
-                    internalState -> internalState.withListener(listener)).state;
+            InternalState oldInternalState = mInternalState.getAndUpdate(
+                    internalState -> internalState.withListener(listener));
+            Preconditions.checkState(listener == null || oldInternalState.listener == null);
+            return oldInternalState.state;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
new file mode 100644
index 0000000..a3ec867
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
@@ -0,0 +1,119 @@
+/*
+ * 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.android.server.location.provider;
+
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.Bundle;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class for wrapping location providers. Subclasses MUST ensure that
+ * {@link #initializeDelegate()} is invoked before the delegate is used.
+ */
+class DelegateLocationProvider extends AbstractLocationProvider
+        implements AbstractLocationProvider.Listener {
+
+    private final Object mInitializationLock = new Object();
+
+    protected final AbstractLocationProvider mDelegate;
+
+    private boolean mInitialized = false;
+
+    DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) {
+        super(executor, null, null);
+
+        mDelegate = delegate;
+    }
+
+    /**
+     * This function initializes state from the delegate and allows all other callbacks to be
+     * immediately invoked. If this method is invoked from a subclass constructor, it should be the
+     * last statement in the constructor since it allows the subclass' reference to escape.
+     */
+    protected void initializeDelegate() {
+        synchronized (mInitializationLock) {
+            Preconditions.checkState(!mInitialized);
+            setState(previousState -> mDelegate.getController().setListener(this));
+            mInitialized = true;
+        }
+    }
+
+    // must be invoked in every listener callback to ensure they don't run until initialized
+    protected final void waitForInitialization() {
+        // callbacks can start coming as soon as the setListener() call in initializeDelegate
+        // completes - but we can't allow any to proceed until the setState call afterwards
+        // completes. acquiring the initialization lock here blocks until initialization is
+        // complete, and we verify this wasn't called before initializeDelegate for some reason.
+        synchronized (mInitializationLock) {
+            Preconditions.checkState(mInitialized);
+        }
+    }
+
+    @Override
+    public void onStateChanged(State oldState, State newState) {
+        waitForInitialization();
+        setState(previousState -> newState);
+    }
+
+    @Override
+    public void onReportLocation(LocationResult locationResult) {
+        waitForInitialization();
+        reportLocation(locationResult);
+    }
+
+    @Override
+    protected void onStart() {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().start();
+    }
+
+    @Override
+    protected void onStop() {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().stop();
+    }
+
+    @Override
+    protected void onSetRequest(ProviderRequest request) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().setRequest(request);
+    }
+
+    @Override
+    protected void onFlush(Runnable callback) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().flush(callback);
+    }
+
+    @Override
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.getController().sendExtraCommand(uid, pid, command, extras);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        Preconditions.checkState(mInitialized);
+        mDelegate.dump(fd, pw, args);
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 48a012e..388b5a4 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -89,6 +89,7 @@
 import com.android.server.LocalServices;
 import com.android.server.location.LocationPermissions;
 import com.android.server.location.LocationPermissions.PermissionLevel;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.fudger.LocationFudger;
 import com.android.server.location.injector.AlarmHelper;
 import com.android.server.location.injector.AppForegroundHelper;
@@ -96,7 +97,6 @@
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationAttributionHelper;
-import com.android.server.location.injector.LocationEventLog;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
@@ -323,7 +323,7 @@
                         + getRequest());
             }
 
-            mLocationEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+            mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
 
             // initialization order is important as there are ordering dependencies
             mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -333,6 +333,10 @@
             mIsUsingHighPower = isUsingHighPower();
 
             onProviderListenerRegister();
+
+            if (mForeground) {
+                mEventLog.logProviderClientForeground(mName, getIdentity());
+            }
         }
 
         @GuardedBy("mLock")
@@ -344,7 +348,7 @@
 
             onProviderListenerUnregister();
 
-            mLocationEventLog.logProviderClientUnregistered(mName, getIdentity());
+            mEventLog.logProviderClientUnregistered(mName, getIdentity());
 
             if (D) {
                 Log.d(TAG, mName + " provider removed registration from " + getIdentity());
@@ -369,6 +373,8 @@
                 Preconditions.checkState(Thread.holdsLock(mLock));
             }
 
+            mEventLog.logProviderClientActive(mName, getIdentity());
+
             if (!getRequest().isHiddenFromAppOps()) {
                 mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
             }
@@ -389,6 +395,8 @@
             }
 
             onProviderListenerInactive();
+
+            mEventLog.logProviderClientInactive(mName, getIdentity());
         }
 
         /**
@@ -524,6 +532,12 @@
 
                 mForeground = foreground;
 
+                if (mForeground) {
+                    mEventLog.logProviderClientForeground(mName, getIdentity());
+                } else {
+                    mEventLog.logProviderClientBackground(mName, getIdentity());
+                }
+
                 // note that onProviderLocationRequestChanged() is always called
                 return onProviderLocationRequestChanged()
                         || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
@@ -855,7 +869,7 @@
 
                     listener.deliverOnLocationChanged(deliverLocationResult,
                             mUseWakeLock ? mWakeLock::release : null);
-                    mLocationEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
+                    mEventLog.logProviderDeliveredLocations(mName, locationResult.size(),
                             getIdentity());
                 }
 
@@ -1154,7 +1168,7 @@
 
                     // we currently don't hold a wakelock for getCurrentLocation deliveries
                     listener.deliverOnLocationChanged(deliverLocationResult, null);
-                    mLocationEventLog.logProviderDeliveredLocations(mName,
+                    mEventLog.logProviderDeliveredLocations(mName,
                             locationResult != null ? locationResult.size() : 0, getIdentity());
                 }
 
@@ -1223,6 +1237,7 @@
 
     private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
 
+    protected final LocationEventLog mEventLog;
     protected final LocationManagerInternal mLocationManagerInternal;
     protected final SettingsHelper mSettingsHelper;
     protected final UserInfoHelper mUserHelper;
@@ -1235,7 +1250,6 @@
     protected final LocationAttributionHelper mLocationAttributionHelper;
     protected final LocationUsageLogger mLocationUsageLogger;
     protected final LocationFudger mLocationFudger;
-    protected final LocationEventLog mLocationEventLog;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final UserSettingChangedListener mLocationEnabledChangedListener =
@@ -1273,8 +1287,8 @@
     @GuardedBy("mLock")
     private @Nullable OnAlarmListener mDelayedRegister;
 
-    public LocationProviderManager(Context context, Injector injector, String name,
-            @Nullable PassiveLocationProviderManager passiveManager) {
+    public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog,
+            String name, @Nullable PassiveLocationProviderManager passiveManager) {
         mContext = context;
         mName = Objects.requireNonNull(name);
         mPassiveManager = passiveManager;
@@ -1285,6 +1299,7 @@
         mEnabledListeners = new ArrayList<>();
         mProviderRequestListeners = new CopyOnWriteArrayList<>();
 
+        mEventLog = eventLog;
         mLocationManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(LocationManagerInternal.class));
         mSettingsHelper = injector.getSettingsHelper();
@@ -1297,7 +1312,6 @@
         mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
         mLocationAttributionHelper = injector.getLocationAttributionHelper();
         mLocationUsageLogger = injector.getLocationUsageLogger();
-        mLocationEventLog = injector.getLocationEventLog();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
 
         mProvider = new MockableLocationProvider(mLock);
@@ -1437,7 +1451,7 @@
         synchronized (mLock) {
             Preconditions.checkState(mState != STATE_STOPPED);
 
-            mLocationEventLog.logProviderMocked(mName, provider != null);
+            mEventLog.logProviderMocked(mName, provider != null);
 
             final long identity = Binder.clearCallingIdentity();
             try {
@@ -1925,7 +1939,7 @@
 
     @GuardedBy("mLock")
     private void setProviderRequest(ProviderRequest request) {
-        mLocationEventLog.logProviderUpdateRequest(mName, request);
+        mEventLog.logProviderUpdateRequest(mName, request);
         mProvider.getController().setRequest(request);
 
         FgThread.getHandler().post(() -> {
@@ -2261,7 +2275,7 @@
             }
 
             // don't log location received for passive provider because it's spammy
-            mLocationEventLog.logProviderReceivedLocations(mName, filtered.size());
+            mEventLog.logProviderReceivedLocations(mName, filtered.size());
         } else {
             // passive provider should get already filtered results as input
             filtered = locationResult;
@@ -2361,7 +2375,7 @@
             if (D) {
                 Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
             }
-            mLocationEventLog.logProviderEnabled(mName, userId, enabled);
+            mEventLog.logProviderEnabled(mName, userId, enabled);
         }
 
         // clear last locations if we become disabled
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..027f4e9 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -24,6 +24,7 @@
 import android.os.Binder;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.Injector;
 
 import java.util.Collection;
@@ -33,8 +34,9 @@
  */
 public class PassiveLocationProviderManager extends LocationProviderManager {
 
-    public PassiveLocationProviderManager(Context context, Injector injector) {
-        super(context, injector, LocationManager.PASSIVE_PROVIDER, null);
+    public PassiveLocationProviderManager(Context context, Injector injector,
+            LocationEventLog eventLog) {
+        super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
new file mode 100644
index 0000000..6f4aa64
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -0,0 +1,294 @@
+/*
+ * 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.android.server.location.provider;
+
+import static android.location.provider.ProviderRequest.INTERVAL_DISABLED;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.FgThread;
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.DeviceIdleHelper;
+import com.android.server.location.injector.DeviceStationaryHelper;
+import com.android.server.location.injector.Injector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Throttles location providers completely while the device is in doze and stationary, and returns
+ * the last known location as a new location at the appropriate interval instead. Hypothetically,
+ * this throttling could be applied only when the device is stationary - however we don't trust the
+ * accuracy of the on-device SMD (which could allow for 100s of feet of movement without triggering
+ * in some use cases) enough to rely just on this. Instead we require the device to be in doze mode
+ * and stationary to narrow down the effect of false positives/negatives.
+ */
+public final class StationaryThrottlingLocationProvider extends DelegateLocationProvider
+        implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
+
+    private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+
+    private final Object mLock = new Object();
+
+    private final String mName;
+    private final DeviceIdleHelper mDeviceIdleHelper;
+    private final DeviceStationaryHelper mDeviceStationaryHelper;
+    private final LocationEventLog mEventLog;
+
+    @GuardedBy("mLock")
+    private boolean mDeviceIdle = false;
+    @GuardedBy("mLock")
+    private boolean mDeviceStationary = false;
+    @GuardedBy("mLock")
+    private long mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+    @GuardedBy("mLock")
+    private ProviderRequest mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+    @GuardedBy("mLock")
+    private ProviderRequest mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+    @GuardedBy("mLock")
+    private long mThrottlingIntervalMs = INTERVAL_DISABLED;
+    @GuardedBy("mLock")
+    private @Nullable DeliverLastLocationRunnable mDeliverLastLocationCallback = null;
+
+    @GuardedBy("mLock")
+    private @Nullable Location mLastLocation;
+
+    public StationaryThrottlingLocationProvider(String name, Injector injector,
+            AbstractLocationProvider delegate, LocationEventLog eventLog) {
+        super(DIRECT_EXECUTOR, delegate);
+
+        mName = name;
+        mDeviceIdleHelper = injector.getDeviceIdleHelper();
+        mDeviceStationaryHelper = injector.getDeviceStationaryHelper();
+        mEventLog = eventLog;
+
+        // must be last statement in the constructor because reference is escaping
+        initializeDelegate();
+    }
+
+    @Override
+    public void onReportLocation(LocationResult locationResult) {
+        super.onReportLocation(locationResult);
+
+        synchronized (mLock) {
+            mLastLocation = locationResult.getLastLocation();
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        mDelegate.getController().start();
+
+        synchronized (mLock) {
+            mDeviceIdleHelper.addListener(this);
+            mDeviceIdle = mDeviceIdleHelper.isDeviceIdle();
+            mDeviceStationaryHelper.addListener(this);
+            mDeviceStationary = false;
+            mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        synchronized (mLock) {
+            mDeviceStationaryHelper.removeListener(this);
+            mDeviceIdleHelper.removeListener(this);
+
+            mIncomingRequest = ProviderRequest.EMPTY_REQUEST;
+            mOutgoingRequest = ProviderRequest.EMPTY_REQUEST;
+            mThrottlingIntervalMs = INTERVAL_DISABLED;
+
+            if (mDeliverLastLocationCallback != null) {
+                FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+                mDeliverLastLocationCallback = null;
+            }
+
+            mLastLocation = null;
+        }
+
+        mDelegate.getController().stop();
+    }
+
+    @Override
+    protected void onSetRequest(ProviderRequest request) {
+        synchronized (mLock) {
+            mIncomingRequest = request;
+            onThrottlingChangedLocked(true);
+        }
+    }
+
+    @Override
+    public void onDeviceIdleChanged(boolean deviceIdle) {
+        synchronized (mLock) {
+            if (deviceIdle == mDeviceIdle) {
+                return;
+            }
+
+            mDeviceIdle = deviceIdle;
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @Override
+    public void onDeviceStationaryChanged(boolean deviceStationary) {
+        synchronized (mLock) {
+            if (mDeviceStationary == deviceStationary) {
+                return;
+            }
+
+            mDeviceStationary = deviceStationary;
+            if (mDeviceStationary) {
+                mDeviceStationaryRealtimeMs = SystemClock.elapsedRealtime();
+            } else {
+                mDeviceStationaryRealtimeMs = Long.MIN_VALUE;
+            }
+            onThrottlingChangedLocked(false);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onThrottlingChangedLocked(boolean deliverImmediate) {
+        long throttlingIntervalMs = INTERVAL_DISABLED;
+        if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+                && mLastLocation != null
+                && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
+                <= MAX_STATIONARY_LOCATION_AGE_MS) {
+            throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+        }
+
+        ProviderRequest newRequest;
+        if (throttlingIntervalMs != INTERVAL_DISABLED) {
+            newRequest = ProviderRequest.EMPTY_REQUEST;
+        } else {
+            newRequest = mIncomingRequest;
+        }
+
+        if (!newRequest.equals(mOutgoingRequest)) {
+            mOutgoingRequest = newRequest;
+            mDelegate.getController().setRequest(mOutgoingRequest);
+        }
+
+        if (throttlingIntervalMs == mThrottlingIntervalMs) {
+            return;
+        }
+
+        long oldThrottlingIntervalMs = mThrottlingIntervalMs;
+        mThrottlingIntervalMs = throttlingIntervalMs;
+
+        if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+            if (oldThrottlingIntervalMs == INTERVAL_DISABLED) {
+                if (D) {
+                    Log.d(TAG, mName + " provider stationary throttled");
+                }
+                mEventLog.logProviderStationaryThrottled(mName, true);
+            }
+
+            if (mDeliverLastLocationCallback != null) {
+                FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+            }
+            mDeliverLastLocationCallback = new DeliverLastLocationRunnable();
+
+            Preconditions.checkState(mLastLocation != null);
+
+            if (deliverImmediate) {
+                FgThread.getHandler().post(mDeliverLastLocationCallback);
+            } else {
+                long delayMs = mThrottlingIntervalMs - mLastLocation.getElapsedRealtimeAgeMillis();
+                FgThread.getHandler().postDelayed(mDeliverLastLocationCallback, delayMs);
+            }
+        } else {
+            if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
+                mEventLog.logProviderStationaryThrottled(mName, false);
+                if (D) {
+                    Log.d(TAG, mName + " provider stationary unthrottled");
+                }
+            }
+
+            FgThread.getHandler().removeCallbacks(mDeliverLastLocationCallback);
+            mDeliverLastLocationCallback = null;
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mThrottlingIntervalMs != INTERVAL_DISABLED) {
+            pw.println("stationary throttled=" + mLastLocation);
+        } else {
+            pw.print("stationary throttled=false");
+            if (!mDeviceIdle) {
+                pw.print(" (not idle)");
+            }
+            if (!mDeviceStationary) {
+                pw.print(" (not stationary)");
+            }
+            pw.println();
+        }
+
+        mDelegate.dump(fd, pw, args);
+    }
+
+    private class DeliverLastLocationRunnable implements Runnable {
+        @Override
+        public void run() {
+            Location location;
+            synchronized (mLock) {
+                if (mDeliverLastLocationCallback != this) {
+                    return;
+                }
+                if (mLastLocation == null) {
+                    return;
+                }
+
+                location = new Location(mLastLocation);
+                location.setTime(System.currentTimeMillis());
+                location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+                if (location.hasSpeed()) {
+                    location.removeSpeed();
+                    if (location.hasSpeedAccuracy()) {
+                        location.removeSpeedAccuracy();
+                    }
+                }
+                if (location.hasBearing()) {
+                    location.removeBearing();
+                    if (location.hasBearingAccuracy()) {
+                        location.removeBearingAccuracy();
+                    }
+                }
+
+                mLastLocation = location;
+                FgThread.getHandler().postDelayed(this, mThrottlingIntervalMs);
+            }
+
+            reportLocation(LocationResult.wrap(location));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 32d637f..4c97f64 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request) {
+    protected void onSetRequest(ProviderRequest request) {
         mRequest = request;
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 28c90e9..685e9e6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -279,6 +279,7 @@
             super.onBootPhase(phase);
             if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.loadEscrowData();
             }
         }
 
@@ -824,13 +825,17 @@
         mSpManager.initWeaverService();
         getAuthSecretHal();
         mDeviceProvisionedObserver.onSystemReady();
-        mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
         mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
                 mInjector.getFaceManager());
     }
 
+    private void loadEscrowData() {
+        mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler);
+    }
+
     private void getAuthSecretHal() {
         try {
             mAuthSecretService = IAuthSecret.getService(/* retry */ true);
@@ -2372,10 +2377,17 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
         enforceShell();
+        final int origPid = Binder.getCallingPid();
+        final int origUid = Binder.getCallingUid();
+
+        // The original identity is an opaque integer.
         final long origId = Binder.clearCallingIdentity();
+        Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid);
         try {
-            (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec(
-                    this, in, out, err, args, callback, resultReceiver);
+            final LockSettingsShellCommand command =
+                    new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid,
+                            origUid);
+            command.exec(this, in, out, err, args, callback, resultReceiver);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 834cf05..6a5c2d89 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -21,8 +21,11 @@
 
 import android.app.ActivityManager;
 import android.app.admin.PasswordMetrics;
+import android.content.Context;
 import android.os.ShellCommand;
+import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -43,15 +46,25 @@
     private static final String COMMAND_VERIFY = "verify";
     private static final String COMMAND_GET_DISABLED = "get-disabled";
     private static final String COMMAND_REMOVE_CACHE = "remove-cache";
+    private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE =
+            "set-resume-on-reboot-provider-package";
     private static final String COMMAND_HELP = "help";
 
     private int mCurrentUserId;
     private final LockPatternUtils mLockPatternUtils;
+    private final Context mContext;
+    private final int mCallingPid;
+    private final int mCallingUid;
+
     private String mOld = "";
     private String mNew = "";
 
-    LockSettingsShellCommand(LockPatternUtils lockPatternUtils) {
+    LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid,
+            int callingUid) {
         mLockPatternUtils = lockPatternUtils;
+        mCallingPid = callingPid;
+        mCallingUid = callingUid;
+        mContext = context;
     }
 
     @Override
@@ -68,6 +81,7 @@
                     case COMMAND_HELP:
                     case COMMAND_GET_DISABLED:
                     case COMMAND_SET_DISABLED:
+                    case COMMAND_SET_ROR_PROVIDER_PACKAGE:
                         break;
                     default:
                         getErrPrintWriter().println(
@@ -80,6 +94,9 @@
                 case COMMAND_REMOVE_CACHE:
                     runRemoveCache();
                     return 0;
+                case COMMAND_SET_ROR_PROVIDER_PACKAGE:
+                    runSetResumeOnRebootProviderPackage();
+                    return 0;
                 case COMMAND_HELP:
                     onHelp();
                     return 0;
@@ -171,6 +188,10 @@
             pw.println("  remove-cache [--user USER_ID]");
             pw.println("    Removes cached unified challenge for the managed profile.");
             pw.println("");
+            pw.println("  set-resume-on-reboot-provider-package <package_name>");
+            pw.println("    Sets the package name for server based resume on reboot service "
+                    + "provider.");
+            pw.println("");
         }
     }
 
@@ -256,6 +277,17 @@
         return true;
     }
 
+    private boolean runSetResumeOnRebootProviderPackage() {
+        final String packageName = mNew;
+        String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE;
+        Slog.i(TAG, "Setting " +  name + " to " + packageName);
+
+        mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE,
+                mCallingPid, mCallingUid, TAG);
+        SystemProperties.set(name, packageName);
+        return true;
+    }
+
     private boolean runClear() {
         LockscreenCredential none = LockscreenCredential.createNone();
         if (!isNewCredentialSufficient(none)) {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
index 38eeb88..af0774c 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -35,6 +35,12 @@
      */
     private static final int CURRENT_VERSION = 2;
 
+    /**
+    * This is the legacy version of the escrow data format for R builds. The escrow data is only
+    * encrypted by the escrow key, without additional wrap of another key from keystore.
+    */
+    private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1;
+
     private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
             RebootEscrowKey key) {
         mSpVersion = spVersion;
@@ -64,6 +70,19 @@
         return mKey;
     }
 
+    private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks,
+            DataInputStream dis) throws IOException {
+        if (kk == null) {
+            throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the"
+                    + " escrow data");
+        }
+
+        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
+        // escrow key.
+        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
+        return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
+    }
+
     static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
             throws IOException {
         Objects.requireNonNull(ks);
@@ -71,17 +90,20 @@
 
         DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
         int version = dis.readInt();
-        if (version != CURRENT_VERSION) {
-            throw new IOException("Unsupported version " + version);
-        }
         byte spVersion = dis.readByte();
-
-        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
-        // escrow key.
-        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
-        final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
-
-        return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+        switch (version) {
+            case CURRENT_VERSION: {
+                byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis);
+                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+            }
+            case LEGACY_SINGLE_ENCRYPTED_VERSION: {
+                // Decrypt the blob with the escrow key directly.
+                byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis);
+                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
+            }
+            default:
+                throw new IOException("Unsupported version " + version);
+        }
     }
 
     static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 06962d4..30ea555 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -39,6 +40,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 import javax.crypto.SecretKey;
 
@@ -76,6 +78,13 @@
     private static final int BOOT_COUNT_TOLERANCE = 5;
 
     /**
+     * The default retry specs for loading reboot escrow data. We will attempt to retry loading
+     * escrow data on temporarily errors, e.g. unavailable network.
+     */
+    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
+    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+
+    /**
      * Logs events for later debugging in bugreports.
      */
     private final RebootEscrowEventLog mEventLog;
@@ -137,6 +146,7 @@
             RebootEscrowProviderInterface rebootEscrowProvider;
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                     "server_based_ror_enabled", false)) {
+                Slog.i(TAG, "Using server based resume on reboot");
                 rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
             } else {
                 rebootEscrowProvider = new RebootEscrowProviderHalImpl();
@@ -148,6 +158,14 @@
             return null;
         }
 
+        void post(Handler handler, Runnable runnable) {
+            handler.post(runnable);
+        }
+
+        void postDelayed(Handler handler, Runnable runnable, long delayMillis) {
+            handler.postDelayed(runnable, delayMillis);
+        }
+
         public Context getContext() {
             return mContext;
         }
@@ -199,7 +217,18 @@
         mKeyStoreManager = injector.getKeyStoreManager();
     }
 
-    void loadRebootEscrowDataIfAvailable() {
+    private void onGetRebootEscrowKeyFailed(List<UserInfo> users) {
+        Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+        for (UserInfo user : users) {
+            mStorage.removeRebootEscrow(user.id);
+        }
+
+        // Clear the old key in keystore.
+        mKeyStoreManager.clearKeyStoreEncryptionKey();
+        onEscrowRestoreComplete(false);
+    }
+
+    void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
         List<UserInfo> users = mUserManager.getUsers();
         List<UserInfo> rebootEscrowUsers = new ArrayList<>();
         for (UserInfo user : users) {
@@ -212,17 +241,53 @@
             return;
         }
 
+        mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
+                retryHandler, 0, users, rebootEscrowUsers));
+    }
+
+    void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber,
+            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+        Objects.requireNonNull(retryHandler);
+
+        final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+                "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
+        final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
+                "load_escrow_data_retry_interval_seconds",
+                DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
+
+        if (attemptNumber < retryLimit) {
+            Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
+            mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry(
+                    retryHandler, attemptNumber, users, rebootEscrowUsers),
+                    retryIntervalInSeconds * 1000);
+            return;
+        }
+
+        Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+        onGetRebootEscrowKeyFailed(users);
+    }
+
+    void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber,
+            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
         // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
         // generated before reboot. Note that we will clear the escrow key even if the keystore key
         // is null.
         SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
-        RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
-        if (kk == null || escrowKey == null) {
-            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
-            for (UserInfo user : users) {
-                mStorage.removeRebootEscrow(user.id);
-            }
-            onEscrowRestoreComplete(false);
+        if (kk == null) {
+            Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
+        }
+
+        RebootEscrowKey escrowKey;
+        try {
+            escrowKey = getAndClearRebootEscrowKey(kk);
+        } catch (IOException e) {
+            scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
+                    rebootEscrowUsers);
+            return;
+        }
+
+        if (escrowKey == null) {
+            onGetRebootEscrowKeyFailed(users);
             return;
         }
 
@@ -249,7 +314,7 @@
         }
     }
 
-    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
+    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
         RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
         if (rebootEscrowProvider == null) {
             Slog.w(TAG,
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
index 6c1040b..4b00772 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java
@@ -33,7 +33,7 @@
  * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL.
  */
 class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface {
-    private static final String TAG = "RebootEscrowProvider";
+    private static final String TAG = "RebootEscrowProviderHal";
 
     private final Injector mInjector;
 
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
index 857ad5f..af6faad 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings;
 
+import java.io.IOException;
+
 import javax.crypto.SecretKey;
 
 /**
@@ -33,9 +35,10 @@
 
     /**
      * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted,
-     * use the input key to decrypt the RebootEscrowKey. Returns null on failure.
+     * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an
+     * IOException if the failure is non-fatal, and a retry may succeed.
      */
-    RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey);
+    RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException;
 
     /**
      * Clears the stored RebootEscrowKey.
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
index ba1a680..b3b4546 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -35,7 +35,7 @@
  * encrypt & decrypt the blob.
  */
 class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
-    private static final String TAG = "RebootEscrowProvider";
+    private static final String TAG = "RebootEscrowProviderServerBased";
 
     // Timeout for service binding
     private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
@@ -50,6 +50,8 @@
 
     private final Injector mInjector;
 
+    private byte[] mServerBlob;
+
     static class Injector {
         private ResumeOnRebootServiceConnection mServiceConnection = null;
 
@@ -124,17 +126,25 @@
     }
 
     @Override
-    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
-        byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException {
+        if (mServerBlob == null) {
+            mServerBlob = mStorage.readRebootEscrowServerBlob();
+        }
         // Delete the server blob in storage.
         mStorage.removeRebootEscrowServerBlob();
-        if (serverBlob == null) {
+        if (mServerBlob == null) {
             Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
             return null;
         }
+        if (decryptionKey == null) {
+            Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is"
+                    + " null.");
+            return null;
+        }
 
+        Slog.i(TAG, "Loaded reboot escrow server blob from storage");
         try {
-            byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+            byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey);
             if (escrowKeyBytes == null) {
                 Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
                 return null;
@@ -145,7 +155,7 @@
             }
 
             return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
-        } catch (TimeoutException | RemoteException | IOException e) {
+        } catch (TimeoutException | RemoteException e) {
             Slog.w(TAG, "Failed to decrypt the server blob ", e);
             return null;
         }
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index a1e18bd..9c471b8 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -31,6 +31,7 @@
 import android.os.ParcelableException;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.service.resumeonreboot.IResumeOnRebootService;
@@ -55,6 +56,10 @@
             Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE;
     private static final String TAG = "ResumeOnRebootServiceProvider";
 
+    // The system property name that overrides the default service provider package name.
+    static final String PROP_ROR_PROVIDER_PACKAGE =
+            "persist.sys.resume_on_reboot_provider_package";
+
     private final Context mContext;
     private final PackageManager mPackageManager;
 
@@ -72,12 +77,19 @@
     private ServiceInfo resolveService() {
         Intent intent = new Intent();
         intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE);
-        if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
-            intent.setPackage(PROVIDER_PACKAGE);
+        int queryFlag = PackageManager.GET_SERVICES;
+        String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, "");
+        if (!testAppName.isEmpty()) {
+            Slog.i(TAG, "Using test app: " + testAppName);
+            intent.setPackage(testAppName);
+        } else {
+            queryFlag |= PackageManager.MATCH_SYSTEM_ONLY;
+            if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) {
+                intent.setPackage(PROVIDER_PACKAGE);
+            }
         }
 
-        List<ResolveInfo> resolvedIntents =
-                mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
+        List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag);
         for (ResolveInfo resolvedInfo : resolvedIntents) {
             if (resolvedInfo.serviceInfo != null
                     && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) {
@@ -120,6 +132,7 @@
             if (mServiceConnection != null) {
                 mContext.unbindService(mServiceConnection);
             }
+            mBinder = null;
         }
 
         /** Bind to the service */
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 74111be..c462a92 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -55,7 +55,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
@@ -120,8 +122,8 @@
     private final Context mContext;
 
     private final Object mLock = new Object();
-    private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders =
-            new ArrayList<>();
+    private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
+            mControllerCallbackHolders = new CopyOnWriteArrayList<>();
 
     private long mFlags;
     private MediaButtonReceiverHolder mMediaButtonReceiverHolder;
@@ -545,51 +547,66 @@
     }
 
     private void pushPlaybackStateUpdate() {
+        PlaybackState playbackState;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onPlaybackStateChanged(mPlaybackState);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushPlaybackStateUpdate",
-                            holder, e);
+            playbackState = mPlaybackState;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onPlaybackStateChanged(playbackState);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushPlaybackStateUpdate", holder,
+                        e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushPlaybackStateUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushMetadataUpdate() {
+        MediaMetadata metadata;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onMetadataChanged(mMetadata);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
+            metadata = mMetadata;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onMetadataChanged(metadata);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushMetadataUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushQueueUpdate() {
+        ParceledListSlice<QueueItem> parcelableQueue;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            ParceledListSlice<QueueItem> parcelableQueue;
             if (mQueue == null) {
                 parcelableQueue = null;
             } else {
@@ -598,77 +615,105 @@
                 // as onQueueChanged is an async binder call.
                 parcelableQueue.setInlineCountLimit(1);
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onQueueChanged(parcelableQueue);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onQueueChanged(parcelableQueue);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushQueueUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushQueueUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushQueueTitleUpdate() {
+        CharSequence queueTitle;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onQueueTitleChanged(mQueueTitle);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushQueueTitleUpdate",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
+            queueTitle = mQueueTitle;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onQueueTitleChanged(queueTitle);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushQueueTitleUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushQueueTitleUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushExtrasUpdate() {
+        Bundle extras;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onExtrasChanged(mExtras);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
+            extras = mExtras;
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onExtrasChanged(extras);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushExtrasUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushExtrasUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushVolumeUpdate() {
+        PlaybackInfo info;
         synchronized (mLock) {
             if (mDestroyed) {
                 return;
             }
-            PlaybackInfo info = getVolumeAttributes();
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onVolumeInfoChanged(info);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
+            info = getVolumeAttributes();
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onVolumeInfoChanged(info);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushVolumeUpdate", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushEvent(String event, Bundle data) {
@@ -676,18 +721,24 @@
             if (mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onEvent(event, data);
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushEvent", holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushEvent", holder, e);
+        }
+        Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null;
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onEvent(event, data);
+            } catch (DeadObjectException e) {
+                if (deadCallbackHolders == null) {
+                    deadCallbackHolders = new ArrayList<>();
                 }
+                deadCallbackHolders.add(holder);
+                logCallbackException("Removing dead callback in pushEvent", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushEvent", holder, e);
             }
         }
+        if (deadCallbackHolders != null) {
+            mControllerCallbackHolders.removeAll(deadCallbackHolders);
+        }
     }
 
     private void pushSessionDestroyed() {
@@ -697,21 +748,18 @@
             if (!mDestroyed) {
                 return;
             }
-            for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
-                ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
-                try {
-                    holder.mCallback.onSessionDestroyed();
-                } catch (DeadObjectException e) {
-                    mControllerCallbackHolders.remove(i);
-                    logCallbackException("Removing dead callback in pushSessionDestroyed",
-                            holder, e);
-                } catch (RemoteException e) {
-                    logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
-                }
-            }
-            // After notifying clear all listeners
-            mControllerCallbackHolders.clear();
         }
+        for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) {
+            try {
+                holder.mCallback.onSessionDestroyed();
+            } catch (DeadObjectException e) {
+                logCallbackException("Removing dead callback in pushSessionDestroyed", holder, e);
+            } catch (RemoteException e) {
+                logCallbackException("unexpected exception in pushSessionDestroyed", holder, e);
+            }
+        }
+        // After notifying clear all listeners
+        mControllerCallbackHolders.clear();
     }
 
     private PlaybackState getStateWithUpdatedPosition() {
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 6d1c680..3cc32be 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -16,10 +16,10 @@
 
 package com.android.server.net;
 
-import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 import static android.provider.Settings.ACTION_VPN_SETTINGS;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,43 +28,37 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
 import android.os.Handler;
 import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
-import com.android.server.ConnectivityService;
-import com.android.server.EventLogTags;
 import com.android.server.connectivity.Vpn;
 
 import java.util.List;
 import java.util.Objects;
 
 /**
- * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
- * connected and kicks off VPN connection, managing any required {@code netd}
- * firewall rules.
+ * State tracker for legacy lockdown VPN. Watches for physical networks to be
+ * connected and kicks off VPN connection.
  */
 public class LockdownVpnTracker {
     private static final String TAG = "LockdownVpnTracker";
 
-    /** Number of VPN attempts before waiting for user intervention. */
-    private static final int MAX_ERROR_COUNT = 4;
-
     public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
 
     @NonNull private final Context mContext;
-    @NonNull private final ConnectivityService mConnService;
+    @NonNull private final ConnectivityManager mCm;
     @NonNull private final NotificationManager mNotificationManager;
     @NonNull private final Handler mHandler;
     @NonNull private final Vpn mVpn;
@@ -76,19 +70,73 @@
     @NonNull private final PendingIntent mConfigIntent;
     @NonNull private final PendingIntent mResetIntent;
 
+    @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback();
+    @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback();
+
+    private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+        private Network mNetwork = null;
+        private LinkProperties mLinkProperties = null;
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            boolean networkChanged = false;
+            if (!network.equals(mNetwork)) {
+                // The default network just changed.
+                mNetwork = network;
+                networkChanged = true;
+            }
+            mLinkProperties = lp;
+            // Backwards compatibility: previously, LockdownVpnTracker only responded to connects
+            // and disconnects, not LinkProperties changes on existing networks.
+            if (networkChanged) {
+                synchronized (mStateLock) {
+                    handleStateChangedLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            // The default network has gone down.
+            mNetwork = null;
+            mLinkProperties = null;
+            synchronized (mStateLock) {
+                handleStateChangedLocked();
+            }
+        }
+
+        public Network getNetwork() {
+            return mNetwork;
+        }
+
+        public LinkProperties getLinkProperties() {
+            return mLinkProperties;
+        }
+    }
+
+    private class VpnNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(Network network) {
+            synchronized (mStateLock) {
+                handleStateChangedLocked();
+            }
+        }
+        @Override
+        public void onLost(Network network) {
+            onAvailable(network);
+        }
+    }
+
     @Nullable
     private String mAcceptedEgressIface;
 
-    private int mErrorCount;
-
     public LockdownVpnTracker(@NonNull Context context,
-            @NonNull ConnectivityService connService,
             @NonNull Handler handler,
             @NonNull KeyStore keyStore,
             @NonNull Vpn vpn,
             @NonNull VpnProfile profile) {
         mContext = Objects.requireNonNull(context);
-        mConnService = Objects.requireNonNull(connService);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
         mHandler = Objects.requireNonNull(handler);
         mVpn = Objects.requireNonNull(vpn);
         mProfile = Objects.requireNonNull(profile);
@@ -110,24 +158,20 @@
      * connection when ready, or setting firewall rules once VPN is connected.
      */
     private void handleStateChangedLocked() {
-
-        final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
-        final LinkProperties egressProp = mConnService.getActiveLinkProperties();
+        final Network network = mDefaultNetworkCallback.getNetwork();
+        final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties();
 
         final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
         final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
 
         // Restart VPN when egress network disconnected or changed
-        final boolean egressDisconnected = egressInfo == null
-                || State.DISCONNECTED.equals(egressInfo.getState());
+        final boolean egressDisconnected = (network == null);
         final boolean egressChanged = egressProp == null
                 || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
 
-        final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType();
         final String egressIface = (egressProp == null) ?
                 null : egressProp.getInterfaceName();
-        Log.d(TAG, "handleStateChanged: egress=" + egressType
-                + " " + mAcceptedEgressIface + "->" + egressIface);
+        Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface);
 
         if (egressDisconnected || egressChanged) {
             mAcceptedEgressIface = null;
@@ -138,46 +182,49 @@
             return;
         }
 
-        if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
-            EventLogTags.writeLockdownVpnError(egressType);
-        }
-
-        if (mErrorCount > MAX_ERROR_COUNT) {
-            showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
-
-        } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
-            if (mProfile.isValidLockdownProfile()) {
-                Log.d(TAG, "Active network connected; starting VPN");
-                EventLogTags.writeLockdownVpnConnecting(egressType);
-                showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
-
-                mAcceptedEgressIface = egressProp.getInterfaceName();
-                try {
-                    // Use the privileged method because Lockdown VPN is initiated by the system, so
-                    // no additional permission checks are necessary.
-                    mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
-                } catch (IllegalStateException e) {
-                    mAcceptedEgressIface = null;
-                    Log.e(TAG, "Failed to start VPN", e);
-                    showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
-                }
-            } else {
+        // At this point, |network| is known to be non-null.
+        if (!vpnInfo.isConnectedOrConnecting()) {
+            if (!mProfile.isValidLockdownProfile()) {
                 Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
                 showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+                return;
             }
 
+            Log.d(TAG, "Active network connected; starting VPN");
+            showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
+
+            mAcceptedEgressIface = egressIface;
+            try {
+                // Use the privileged method because Lockdown VPN is initiated by the system, so
+                // no additional permission checks are necessary.
+                //
+                // Pass in the underlying network here because the legacy VPN is, in fact, tightly
+                // coupled to a given underlying network and cannot provide mobility. This makes
+                // things marginally more correct in two ways:
+                //
+                // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra
+                //    CONNECTED broadcast for the underlying network type. The underlying type comes
+                //    from here. LTT *could* assume that the underlying network is the default
+                //    network, but that might introduce a race condition if, say, the VPN starts
+                //    connecting on cell, but when the connection succeeds and the agent is
+                //    registered, the default network is now wifi.
+                // 2. If no underlying network is passed in, then CS will assume the underlying
+                //    network is the system default. So, if the VPN  is up and underlying network
+                //    (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
+                //    changed to match the new default network (e.g., cell).
+                mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
+            } catch (IllegalStateException e) {
+                mAcceptedEgressIface = null;
+                Log.e(TAG, "Failed to start VPN", e);
+                showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+            }
         } else if (vpnInfo.isConnected() && vpnConfig != null) {
             final String iface = vpnConfig.interfaze;
             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
 
             Log.d(TAG, "VPN connected using iface=" + iface
                     + ", sourceAddr=" + sourceAddrs.toString());
-            EventLogTags.writeLockdownVpnConnected(egressType);
             showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
-
-            final NetworkInfo clone = new NetworkInfo(egressInfo);
-            augmentNetworkInfo(clone);
-            mConnService.sendConnectedBroadcast(clone);
         }
     }
 
@@ -192,7 +239,15 @@
 
         mVpn.setEnableTeardown(false);
         mVpn.setLockdown(true);
+        mCm.setLegacyLockdownVpnEnabled(true);
         handleStateChangedLocked();
+
+        mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+        final NetworkRequest vpnRequest = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(TRANSPORT_VPN)
+                .build();
+        mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler);
     }
 
     public void shutdown() {
@@ -205,20 +260,21 @@
         Log.d(TAG, "shutdownLocked()");
 
         mAcceptedEgressIface = null;
-        mErrorCount = 0;
 
         mVpn.stopVpnRunnerPrivileged();
         mVpn.setLockdown(false);
+        mCm.setLegacyLockdownVpnEnabled(false);
         hideNotification();
 
         mVpn.setEnableTeardown(true);
+        mCm.unregisterNetworkCallback(mDefaultNetworkCallback);
+        mCm.unregisterNetworkCallback(mVpnNetworkCallback);
     }
 
     /**
      * Reset VPN lockdown tracker. Called by ConnectivityService when receiving
      * {@link #ACTION_LOCKDOWN_RESET} pending intent.
      */
-    @GuardedBy("mConnService.mVpns")
     public void reset() {
         Log.d(TAG, "reset()");
         synchronized (mStateLock) {
@@ -229,28 +285,6 @@
         }
     }
 
-    public void onNetworkInfoChanged() {
-        synchronized (mStateLock) {
-            handleStateChangedLocked();
-        }
-    }
-
-    public void onVpnStateChanged(NetworkInfo info) {
-        if (info.getDetailedState() == DetailedState.FAILED) {
-            mErrorCount++;
-        }
-        synchronized (mStateLock) {
-            handleStateChangedLocked();
-        }
-    }
-
-    public void augmentNetworkInfo(NetworkInfo info) {
-        if (info.isConnected()) {
-            final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
-            info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
-        }
-    }
-
     private void showNotification(int titleRes, int iconRes) {
         final Notification.Builder builder =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN)
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 769b781..78c1a95 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -197,6 +198,11 @@
     }
 
     @Override
+    protected void ensureFilters(ServiceInfo si, int userId) {
+        // nothing to filter
+    }
+
+    @Override
     protected void loadDefaultsFromConfig() {
         String defaultDndAccess = mContext.getResources().getString(
                 R.string.config_defaultDndAccessPackages);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 21537e6..bbdcac2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -189,6 +189,8 @@
 
     abstract protected void onServiceAdded(ManagedServiceInfo info);
 
+    abstract protected void ensureFilters(ServiceInfo si, int userId);
+
     protected List<ManagedServiceInfo> getServices() {
         synchronized (mMutex) {
             List<ManagedServiceInfo> services = new ArrayList<>(mServices);
@@ -1342,8 +1344,10 @@
             for (ComponentName component : add) {
                 try {
                     ServiceInfo info = mPm.getServiceInfo(component,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                            PackageManager.GET_META_DATA
+                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                            userId);
                     if (info == null) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": service not found");
@@ -1356,7 +1360,7 @@
                     }
                     Slog.v(TAG,
                             "enabling " + getCaption() + " for " + userId + ": " + component);
-                    registerService(component, userId);
+                    registerService(info, userId);
                 } catch (RemoteException e) {
                     e.rethrowFromSystemServer();
                 }
@@ -1368,9 +1372,15 @@
      * Version of registerService that takes the name of a service component to bind to.
      */
     @VisibleForTesting
-    void registerService(final ComponentName name, final int userid) {
+    void registerService(final ServiceInfo si, final int userId) {
+        ensureFilters(si, userId);
+        registerService(si.getComponentName(), userId);
+    }
+
+    @VisibleForTesting
+    void registerService(final ComponentName cn, final int userId) {
         synchronized (mMutex) {
-            registerServiceLocked(name, userid);
+            registerServiceLocked(cn, userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4c3dfbf..917be29 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -110,6 +110,8 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -175,6 +177,7 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.media.AudioAttributes;
@@ -212,6 +215,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
+import android.service.notification.ConditionProviderService;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
@@ -284,7 +288,6 @@
 import com.android.server.notification.toast.TextToastRecord;
 import com.android.server.notification.toast.ToastRecord;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.quota.MultiRateLimiter;
@@ -358,7 +361,7 @@
     private static final int MESSAGE_RECONSIDER_RANKING = 1000;
     private static final int MESSAGE_RANKING_SORT = 1001;
 
-    static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+    static final int LONG_DELAY = TOAST_WINDOW_TIMEOUT - TOAST_WINDOW_ANIM_BUFFER; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
 
     // 1 second past the ANR timeout.
@@ -3056,7 +3059,7 @@
                     // If the callback fails, this will remove it from the list, so don't
                     // assume that it's valid after this.
                     if (index == 0) {
-                        showNextToastLocked();
+                        showNextToastLocked(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(callingId);
@@ -7392,7 +7395,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    void showNextToastLocked() {
+    void showNextToastLocked(boolean lastToastWasTextRecord) {
         if (mIsCurrentToastShown) {
             return; // Don't show the same toast twice.
         }
@@ -7406,7 +7409,7 @@
                     mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);
 
             if (tryShowToast(record, rateLimitingEnabled, isWithinQuota)) {
-                scheduleDurationReachedLocked(record);
+                scheduleDurationReachedLocked(record, lastToastWasTextRecord);
                 mIsCurrentToastShown = true;
                 if (rateLimitingEnabled) {
                     mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
@@ -7477,7 +7480,7 @@
             // Show the next one. If the callback fails, this will remove
             // it from the list, so don't assume that the list hasn't changed
             // after this point.
-            showNextToastLocked();
+            showNextToastLocked(lastToast instanceof TextToastRecord);
         }
     }
 
@@ -7491,7 +7494,7 @@
     }
 
     @GuardedBy("mToastQueue")
-    private void scheduleDurationReachedLocked(ToastRecord r)
+    private void scheduleDurationReachedLocked(ToastRecord r, boolean lastToastWasTextRecord)
     {
         mHandler.removeCallbacksAndMessages(r);
         Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
@@ -7501,6 +7504,14 @@
         // preference.
         delay = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                 AccessibilityManager.FLAG_CONTENT_TEXT);
+
+        if (lastToastWasTextRecord) {
+            delay += 250; // delay to account for previous toast's "out" animation
+        }
+        if (r instanceof TextToastRecord) {
+            delay += 333; // delay to account for this toast's "in" animation
+        }
+
         mHandler.sendMessageDelayed(m, delay);
     }
 
@@ -8960,7 +8971,8 @@
         NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey);
         if (nls != null
                 && (!nls.isTypeAllowed(notificationType)
-                || !nls.isPackageAllowed(sbn.getPackageName()))) {
+                || !nls.isPackageAllowed(
+                        new VersionedPackage(sbn.getPackageName(), sbn.getUid())))) {
             return false;
         }
         return true;
@@ -9123,6 +9135,12 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            // nothing to filter; no user visible settings for types/packages like other
+            // listeners
+        }
+
+        @Override
         @GuardedBy("mNotificationLock")
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             mListeners.unregisterService(removed.service, removed.userid);
@@ -9567,11 +9585,12 @@
 
     public class NotificationListeners extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
-        static final String TAG_REQUESTED_LISTENERS = "req_listeners";
+        static final String TAG_REQUESTED_LISTENERS = "request_listeners";
         static final String TAG_REQUESTED_LISTENER = "listener";
         static final String ATT_COMPONENT = "component";
         static final String ATT_TYPES = "types";
-        static final String ATT_PKGS = "pkgs";
+        static final String ATT_PKG = "pkg";
+        static final String ATT_UID = "uid";
         static final String TAG_APPROVED = "allowed";
         static final String TAG_DISALLOWED= "disallowed";
         static final String XML_SEPARATOR = ",";
@@ -9689,28 +9708,6 @@
         }
 
         @Override
-        public void onUserUnlocked(int user) {
-            int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
-
-            final PackageManager pmWrapper = mContext.getPackageManager();
-            List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
-                    new Intent(getConfig().serviceInterface), flags, user);
-
-            for (ResolveInfo resolveInfo : installedServices) {
-                ServiceInfo info = resolveInfo.serviceInfo;
-
-                if (!getConfig().bindPermission.equals(info.permission)) {
-                    continue;
-                }
-                Pair key = Pair.create(info.getComponentName(), user);
-                if (!mRequestedNotificationListeners.containsKey(key)) {
-                    mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
-                }
-            }
-            super.onUserUnlocked(user);
-        }
-
-        @Override
         public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
             super.onPackagesChanged(removingPackage, pkgList, uidList);
 
@@ -9729,6 +9726,18 @@
                     }
                 }
             }
+
+            // clean up anything in the disallowed pkgs list
+            for (int i = 0; i < pkgList.length; i++) {
+                String pkg = pkgList[i];
+                int userId = UserHandle.getUserId(uidList[i]);
+                for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
+                    NotificationListenerFilter nlf = mRequestedNotificationListeners.valueAt(j);
+
+                    VersionedPackage ai = new VersionedPackage(pkg, uidList[i]);
+                    nlf.removePackage(ai);
+                }
+            }
         }
 
         @Override
@@ -9758,15 +9767,17 @@
                     int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING
                             | FLAG_FILTER_TYPE_SILENT | FLAG_FILTER_TYPE_ONGOING;
 
-                    ArraySet<String> disallowedPkgs = new ArraySet<>();
+                    ArraySet<VersionedPackage> disallowedPkgs = new ArraySet<>();
                     final int listenerOuterDepth = parser.getDepth();
                     while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) {
                         if (TAG_APPROVED.equals(parser.getName())) {
                             approved = XmlUtils.readIntAttribute(parser, ATT_TYPES);
                         } else if (TAG_DISALLOWED.equals(parser.getName())) {
-                            String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS);
-                            if (!TextUtils.isEmpty(pkgs)) {
-                                disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR));
+                            String pkg = XmlUtils.readStringAttribute(parser, ATT_PKG);
+                            int uid = XmlUtils.readIntAttribute(parser, ATT_UID);
+                            if (!TextUtils.isEmpty(pkg)) {
+                                VersionedPackage ai = new VersionedPackage(pkg, uid);
+                                disallowedPkgs.add(ai);
                             }
                         }
                     }
@@ -9791,10 +9802,14 @@
                 XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes());
                 out.endTag(null, TAG_APPROVED);
 
-                out.startTag(null, TAG_DISALLOWED);
-                XmlUtils.writeStringAttribute(
-                        out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages()));
-                out.endTag(null, TAG_DISALLOWED);
+                for (VersionedPackage ai : nlf.getDisallowedPackages()) {
+                    if (!TextUtils.isEmpty(ai.getPackageName())) {
+                        out.startTag(null, TAG_DISALLOWED);
+                        XmlUtils.writeStringAttribute(out, ATT_PKG, ai.getPackageName());
+                        XmlUtils.writeIntAttribute(out, ATT_UID, ai.getVersionCode());
+                        out.endTag(null, TAG_DISALLOWED);
+                    }
+                }
 
                 out.endTag(null, TAG_REQUESTED_LISTENER);
             }
@@ -9812,6 +9827,38 @@
             mRequestedNotificationListeners.put(pair, nlf);
         }
 
+        @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+            Pair listener = Pair.create(si.getComponentName(), userId);
+            NotificationListenerFilter existingNlf =
+                    mRequestedNotificationListeners.get(listener);
+            if (existingNlf  == null) {
+                // no stored filters for this listener; see if they provided a default
+                if (si.metaData != null) {
+                    String typeList = si.metaData.getString(
+                            NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES);
+                    if (typeList != null) {
+                        int types = 0;
+                        String[] typeStrings = typeList.split(XML_SEPARATOR);
+                        for (int i = 0; i < typeStrings.length; i++) {
+                            if (TextUtils.isEmpty(typeStrings[i])) {
+                                continue;
+                            }
+                            try {
+                                types |= Integer.parseInt(typeStrings[i]);
+                            } catch (NumberFormatException e) {
+                                // skip
+                            }
+                        }
+
+                         NotificationListenerFilter nlf =
+                                 new NotificationListenerFilter(types, new ArraySet<>());
+                        mRequestedNotificationListeners.put(listener, nlf);
+                    }
+                }
+            }
+        }
+
         @GuardedBy("mNotificationLock")
         public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
             if (trim == TRIM_LIGHT) {
diff --git a/services/core/java/com/android/server/om/DumpState.java b/services/core/java/com/android/server/om/DumpState.java
index 1e2e054..88afcb2 100644
--- a/services/core/java/com/android/server/om/DumpState.java
+++ b/services/core/java/com/android/server/om/DumpState.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.om.OverlayIdentifier;
 import android.os.UserHandle;
 
 /**
@@ -26,6 +27,7 @@
 public final class DumpState {
     @UserIdInt private int mUserId = UserHandle.USER_ALL;
     @Nullable private String mPackageName;
+    @Nullable private String mOverlayName;
     @Nullable private String mField;
     private boolean mVerbose;
 
@@ -38,14 +40,19 @@
     }
 
     /** Sets the name of the package to dump the state for */
-    public void setPackageName(String packageName) {
-        mPackageName = packageName;
+    public void setOverlyIdentifier(String overlayIdentifier) {
+        final OverlayIdentifier overlay = OverlayIdentifier.fromString(overlayIdentifier);
+        mPackageName = overlay.getPackageName();
+        mOverlayName = overlay.getOverlayName();
     }
     @Nullable public String getPackageName() {
         return mPackageName;
     }
+    @Nullable public String getOverlayName() {
+        return mOverlayName;
+    }
 
-    /** Sets the name of the field to dump the state of */
+    /** Sets the name of the field to dump the state for */
     public void setField(String field) {
         mField = field;
     }
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 19fa920..2ebc8ed 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -20,17 +20,23 @@
 
 import static com.android.server.om.OverlayManagerService.TAG;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.IBinder;
 import android.os.IIdmap2;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemService;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.server.FgThread;
 
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -104,10 +110,12 @@
         return sInstance;
     }
 
-    String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-            int userId) throws TimeoutException, RemoteException {
+    String createIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
+            throws TimeoutException, RemoteException {
         try (Connection c = connect()) {
-            return mService.createIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -117,11 +125,12 @@
         }
     }
 
-    boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-             int userId)
+    boolean verifyIdmap(@NonNull String targetPath, @NonNull String overlayPath,
+            @Nullable String overlayName, int policies, boolean enforce, int userId)
             throws Exception {
         try (Connection c = connect()) {
-            return mService.verifyIdmap(targetPath, overlayPath, policies, enforce, userId);
+            return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+                    policies, enforce, userId);
         }
     }
 
@@ -129,12 +138,38 @@
         try (Connection c = connect()) {
             return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
         } catch (Exception e) {
-            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
-                    + e.getMessage());
+            Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
             return false;
         }
     }
 
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        try (Connection c = connect()) {
+            return mService.createFabricatedOverlay(overlay);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
+            return null;
+        }
+    }
+
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        try (Connection c = connect()) {
+            return mService.deleteFabricatedOverlay(path);
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
+            return false;
+        }
+    }
+
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        try (Connection c = connect()) {
+            return mService.getFabricatedOverlayInfos();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "failed to get fabricated overlays", e);
+            return null;
+        }
+    }
+
     private IBinder getIdmapService() throws TimeoutException, RemoteException {
         SystemService.start(IDMAP_DAEMON);
 
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index eeb2655..64362c9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -22,15 +22,19 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.content.pm.PackageParser;
 import android.os.Build.VERSION_CODES;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.os.OverlayablePolicy;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Slog;
 
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
 import java.io.IOException;
+import java.util.List;
 
 /**
  * Handle the creation and deletion of idmap files.
@@ -74,25 +78,26 @@
      * Creates the idmap for the target/overlay combination and returns whether the idmap file was
      * modified.
      */
-    boolean createIdmap(@NonNull final PackageInfo targetPackage,
-            @NonNull final PackageInfo overlayPackage, int userId) {
+    boolean createIdmap(@NonNull final AndroidPackage targetPackage,
+            @NonNull final AndroidPackage overlayPackage, String overlayBasePath,
+            String overlayName, int userId) {
         if (DEBUG) {
-            Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
-                    + overlayPackage.packageName);
+            Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
+                    + overlayPackage.getPackageName());
         }
-        final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
-        final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
+        final String targetPath = targetPackage.getBaseApkPath();
         try {
             int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
-            if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
+            if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
+                    enforce, userId)) {
                 return false;
             }
-            return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
+            return mIdmapDaemon.createIdmap(targetPath, overlayBasePath, overlayName, policies,
                     enforce, userId) != null;
         } catch (Exception e) {
             Slog.w(TAG, "failed to generate idmap for " + targetPath + " and "
-                    + overlayPath + ": " + e.getMessage());
+                    + overlayBasePath, e);
             return false;
         }
     }
@@ -104,7 +109,7 @@
         try {
             return mIdmapDaemon.removeIdmap(oi.baseCodePath, userId);
         } catch (Exception e) {
-            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath + ": " + e.getMessage());
+            Slog.w(TAG, "failed to remove idmap for " + oi.baseCodePath, e);
             return false;
         }
     }
@@ -113,22 +118,40 @@
         return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
     }
 
-    boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
-        return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
+    /**
+     * @return the list of all fabricated overlays
+     */
+    List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        return mIdmapDaemon.getFabricatedOverlayInfos();
+    }
+
+    /**
+     * Creates a fabricated overlay and persists it to disk.
+     * @return the path to the fabricated overlay
+     */
+    FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+        return mIdmapDaemon.createFabricatedOverlay(overlay);
+    }
+
+    /**
+     * Deletes the fabricated overlay file on disk.
+     * @return whether the path was deleted
+     */
+    boolean deleteFabricatedOverlay(@NonNull String path) {
+        return mIdmapDaemon.deleteFabricatedOverlay(path);
     }
 
     /**
      * Checks if overlayable and policies should be enforced on the specified overlay for backwards
      * compatibility with pre-Q overlays.
      */
-    private boolean enforceOverlayable(@NonNull final PackageInfo overlayPackage) {
-        final ApplicationInfo ai = overlayPackage.applicationInfo;
-        if (ai.targetSdkVersion >= VERSION_CODES.Q) {
+    private boolean enforceOverlayable(@NonNull final AndroidPackage overlayPackage) {
+        if (overlayPackage.getTargetSdkVersion() >= VERSION_CODES.Q) {
             // Always enforce policies for overlays targeting Q+.
             return true;
         }
 
-        if (ai.isVendor()) {
+        if (overlayPackage.isVendor()) {
             // If the overlay is on a pre-Q vendor partition, do not enforce overlayable
             // restrictions on this overlay because the pre-Q platform has no understanding of
             // overlayable.
@@ -137,20 +160,19 @@
 
         // Do not enforce overlayable restrictions on pre-Q overlays that are signed with the
         // platform signature or that are preinstalled.
-        return !(ai.isSystemApp() || ai.isSignedWithPlatformKey());
+        return !(overlayPackage.isSystem() || overlayPackage.isSignedWithPlatformKey());
     }
 
     /**
      * Retrieves a bitmask for idmap2 that represents the policies the overlay fulfills.
      */
-    private int calculateFulfilledPolicies(@NonNull final PackageInfo targetPackage,
-            @NonNull final PackageInfo overlayPackage, int userId)  {
-        final ApplicationInfo ai = overlayPackage.applicationInfo;
+    private int calculateFulfilledPolicies(@NonNull final AndroidPackage targetPackage,
+            @NonNull final AndroidPackage overlayPackage, int userId)  {
         int fulfilledPolicies = OverlayablePolicy.PUBLIC;
 
         // Overlay matches target signature
-        if (mPackageManager.signaturesMatching(targetPackage.packageName,
-                overlayPackage.packageName, userId)) {
+        if (mPackageManager.signaturesMatching(targetPackage.getPackageName(),
+                overlayPackage.getPackageName(), userId)) {
             fulfilledPolicies |= OverlayablePolicy.SIGNATURE;
         }
 
@@ -164,52 +186,52 @@
         // preinstalled package, check if overlay matches its signature.
         if (!TextUtils.isEmpty(mConfigSignaturePackage)
                 && mPackageManager.signaturesMatching(mConfigSignaturePackage,
-                                                           overlayPackage.packageName,
+                                                           overlayPackage.getPackageName(),
                                                            userId)) {
             fulfilledPolicies |= OverlayablePolicy.CONFIG_SIGNATURE;
         }
 
         // Vendor partition (/vendor)
-        if (ai.isVendor()) {
+        if (overlayPackage.isVendor()) {
             return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
         }
 
         // Product partition (/product)
-        if (ai.isProduct()) {
+        if (overlayPackage.isProduct()) {
             return fulfilledPolicies | OverlayablePolicy.PRODUCT_PARTITION;
         }
 
         // Odm partition (/odm)
-        if (ai.isOdm()) {
+        if (overlayPackage.isOdm()) {
             return fulfilledPolicies | OverlayablePolicy.ODM_PARTITION;
         }
 
         // Oem partition (/oem)
-        if (ai.isOem()) {
+        if (overlayPackage.isOem()) {
             return fulfilledPolicies | OverlayablePolicy.OEM_PARTITION;
         }
 
         // System_ext partition (/system_ext) is considered as system
         // Check this last since every partition except for data is scanned as system in the PMS.
-        if (ai.isSystemApp() || ai.isSystemExt()) {
+        if (overlayPackage.isSystem() || overlayPackage.isSystemExt()) {
             return fulfilledPolicies | OverlayablePolicy.SYSTEM_PARTITION;
         }
 
         return fulfilledPolicies;
     }
 
-    private boolean matchesActorSignature(@NonNull PackageInfo targetPackage,
-            @NonNull PackageInfo overlayPackage, int userId) {
-        String targetOverlayableName = overlayPackage.targetOverlayableName;
+    private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
+            @NonNull AndroidPackage overlayPackage, int userId) {
+        String targetOverlayableName = overlayPackage.getOverlayTargetName();
         if (targetOverlayableName != null) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
-                        targetPackage.packageName, targetOverlayableName, userId);
+                        targetPackage.getPackageName(), targetOverlayableName, userId);
                 if (overlayableInfo != null && overlayableInfo.actor != null) {
                     String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
                             overlayableInfo.actor, mPackageManager.getNamedActors()).first;
                     if (mPackageManager.signaturesMatching(actorPackageName,
-                            overlayPackage.packageName, userId)) {
+                            overlayPackage.getPackageName(), userId)) {
                         return true;
                     }
                 }
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index 8121a43e9..2d540de 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,8 +19,6 @@
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.net.Uri;
 import android.os.Process;
 import android.text.TextUtils;
@@ -29,6 +27,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.IOException;
 import java.util.List;
@@ -114,12 +113,13 @@
         }
 
         final String targetPackageName = overlayInfo.targetPackageName;
-        final PackageInfo targetPkgInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
+        final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
+                userId);
         if (targetPkgInfo == null) {
             return ActorState.TARGET_NOT_FOUND;
         }
 
-        if ((targetPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+        if (targetPkgInfo.isDebuggable()) {
             return ActorState.ALLOWED;
         }
 
@@ -189,23 +189,18 @@
             return actorUriState;
         }
 
-        String packageName = actorUriPair.first;
-        PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, userId);
-        if (packageInfo == null) {
-            return ActorState.MISSING_APP_INFO;
-        }
-
-        ApplicationInfo appInfo = packageInfo.applicationInfo;
-        if (appInfo == null) {
-            return ActorState.MISSING_APP_INFO;
+        String actorPackageName = actorUriPair.first;
+        AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
+        if (actorPackage == null) {
+            return ActorState.ACTOR_NOT_FOUND;
         }
 
         // Currently only pre-installed apps can be actors
-        if (!appInfo.isSystemApp()) {
+        if (!actorPackage.isSystem()) {
             return ActorState.ACTOR_NOT_PREINSTALLED;
         }
 
-        if (ArrayUtils.contains(callingPackageNames, packageName)) {
+        if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
             return ActorState.ALLOWED;
         }
 
@@ -231,7 +226,7 @@
         NO_NAMED_ACTORS,
         MISSING_NAMESPACE,
         MISSING_ACTOR_NAME,
-        MISSING_APP_INFO,
+        ACTOR_NOT_FOUND,
         ACTOR_NOT_PREINSTALLED,
         INVALID_ACTOR,
         ALLOWED
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index fd2fb1f..905733c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -24,8 +24,10 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_REASON;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_REGISTER_FABRICATED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
 import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
@@ -43,11 +45,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayManagerTransaction;
 import android.content.om.OverlayableInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.overlay.OverlayPaths;
@@ -55,8 +57,10 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.FabricatedOverlayInternal;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -71,12 +75,15 @@
 import android.util.SparseArray;
 
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
+
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import libcore.util.EmptyArray;
 
@@ -92,13 +99,12 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Consumer;
 
 /**
  * Service to manage asset overlays.
@@ -247,15 +253,6 @@
 
     private final OverlayActorEnforcer mActorEnforcer;
 
-    private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
-        persistSettings();
-        FgThread.getHandler().post(() -> {
-            List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
-            updateActivityManager(affectedTargets, pair.userId);
-            broadcastActionOverlayChanged(pair.packageName, pair.userId);
-        });
-    };
-
     public OverlayManagerService(@NonNull final Context context) {
         super(context);
         try {
@@ -315,8 +312,7 @@
                     // Initialize any users that can't be switched to, as their state would
                     // never be setup in onSwitchUser(). We will switch to the system user right
                     // after this, and its state will be setup there.
-                    final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
-                    updatePackageManager(targets, users.get(i).id);
+                    updatePackageManager(mImpl.updateOverlaysForUser(users.get(i).id));
                 }
             }
         }
@@ -333,9 +329,7 @@
             // ensure overlays in the settings are up-to-date, and propagate
             // any asset changes to the rest of the system
             synchronized (mLock) {
-                final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
-                final List<String> affectedTargets = updatePackageManager(targets, newUserId);
-                updateActivityManager(affectedTargets, newUserId);
+                updateActivityManager(updatePackageManager(mImpl.updateOverlaysForUser(newUserId)));
             }
             persistSettings();
         } finally {
@@ -417,19 +411,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
                 for (final int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+                        final AndroidPackage pkg = mPackageManager.onPackageAdded(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageAdded(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                } else {
-                                    mImpl.onTargetPackageAdded(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageAdded(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageAdded internal error", e);
                             }
@@ -447,19 +433,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
-
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageChanged(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }  else {
-                                    mImpl.onTargetPackageChanged(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageChanged(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageChanged internal error", e);
                             }
@@ -477,12 +455,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        mPackageManager.forgetPackageInfo(packageName, userId);
-                        final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-                        if (oi != null) {
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                mImpl.onOverlayPackageReplacing(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
+                                updateTargetPackages(mImpl.onPackageReplacing(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageReplacing internal error", e);
                             }
@@ -500,18 +477,11 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
-                                false);
-                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
-                            mPackageManager.cachePackageInfo(packageName, userId, pi);
+                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
+                                packageName, userId);
+                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
                             try {
-                                if (pi.isOverlayPackage()) {
-                                    mImpl.onOverlayPackageReplaced(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                } else {
-                                    mImpl.onTargetPackageReplaced(packageName, userId)
-                                        .ifPresent(mPropagateOverlayChange);
-                                }
+                                updateTargetPackages(mImpl.onPackageReplaced(packageName, userId));
                             } catch (OperationFailedException e) {
                                 Slog.e(TAG, "onPackageReplaced internal error", e);
                             }
@@ -529,20 +499,8 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        mPackageManager.forgetPackageInfo(packageName, userId);
-                        final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-
-                        try {
-                            if (oi != null) {
-                                mImpl.onOverlayPackageRemoved(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
-                            } else {
-                                mImpl.onTargetPackageRemoved(packageName, userId)
-                                    .ifPresent(mPropagateOverlayChange);
-                            }
-                        } catch (OperationFailedException e) {
-                            Slog.e(TAG, "onPackageRemoved internal error", e);
-                        }
+                        mPackageManager.onPackageRemoved(packageName, userId);
+                        updateTargetPackages(mImpl.onPackageRemoved(packageName, userId));
                     }
                 }
             } finally {
@@ -560,11 +518,9 @@
                     if (userId != UserHandle.USER_NULL) {
                         try {
                             traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED");
-                            final ArrayList<String> targets;
                             synchronized (mLock) {
-                                targets = mImpl.updateOverlaysForUser(userId);
+                                updatePackageManager(mImpl.updateOverlaysForUser(userId));
                             }
-                            updatePackageManager(targets, userId);
                         } finally {
                             traceEnd(TRACE_TAG_RRO);
                         }
@@ -628,16 +584,22 @@
         @Override
         public OverlayInfo getOverlayInfo(@Nullable final String packageName,
                 final int userIdArg) {
-            if (packageName == null) {
+            return getOverlayInfoByIdentifier(new OverlayIdentifier(packageName), userIdArg);
+        }
+
+        @Override
+        public OverlayInfo getOverlayInfoByIdentifier(@Nullable final OverlayIdentifier overlay,
+                final int userIdArg) {
+            if (overlay == null || overlay.getPackageName() == null) {
                 return null;
             }
 
             try {
-                traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + packageName);
+                traceBegin(TRACE_TAG_RRO, "OMS#getOverlayInfo " + overlay);
                 final int realUserId = handleIncomingUser(userIdArg, "getOverlayInfo");
 
                 synchronized (mLock) {
-                    return mImpl.getOverlayInfo(packageName, realUserId);
+                    return mImpl.getOverlayInfo(overlay, realUserId);
                 }
             } finally {
                 traceEnd(TRACE_TAG_RRO);
@@ -653,15 +615,16 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setEnabled");
-                enforceActor(packageName, "setEnabled", realUserId);
+                enforceActor(overlay, "setEnabled", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabled(packageName, enable, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            updateTargetPackages(mImpl.setEnabled(overlay, enable, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -684,16 +647,18 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setEnabledExclusive");
-                enforceActor(packageName, "setEnabledExclusive", realUserId);
+                enforceActor(overlay, "setEnabledExclusive", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabledExclusive(packageName,
+                            mImpl.setEnabledExclusive(overlay,
                                     false /* withinCategory */, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -716,17 +681,19 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg,
                         "setEnabledExclusiveInCategory");
-                enforceActor(packageName, "setEnabledExclusiveInCategory", realUserId);
+                enforceActor(overlay, "setEnabledExclusiveInCategory", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setEnabledExclusive(packageName,
+                            mImpl.setEnabledExclusive(overlay,
                                     true /* withinCategory */, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -750,15 +717,18 @@
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
                         + parentPackageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
+                final OverlayIdentifier parentOverlay = new OverlayIdentifier(parentPackageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setPriority");
-                enforceActor(packageName, "setPriority", realUserId);
+                enforceActor(overlay, "setPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setPriority(packageName, parentPackageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            mImpl.setPriority(overlay, parentOverlay, realUserId)
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -780,15 +750,16 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setHighestPriority");
-                enforceActor(packageName, "setHighestPriority", realUserId);
+                enforceActor(overlay, "setHighestPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setHighestPriority(packageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            updateTargetPackages(mImpl.setHighestPriority(overlay, realUserId));
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -810,15 +781,17 @@
 
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
+
+                final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
                 final int realUserId = handleIncomingUser(userIdArg, "setLowestPriority");
-                enforceActor(packageName, "setLowestPriority", realUserId);
+                enforceActor(overlay, "setLowestPriority", realUserId);
 
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
                         try {
-                            mImpl.setLowestPriority(packageName, realUserId)
-                                .ifPresent(mPropagateOverlayChange);
+                            mImpl.setLowestPriority(overlay, realUserId)
+                                .ifPresent(OverlayManagerService.this::updateTargetPackages);
                             return true;
                         } catch (OperationFailedException e) {
                             return false;
@@ -858,12 +831,17 @@
                 return;
             }
 
+            final OverlayIdentifier overlay = new OverlayIdentifier(packageName);
             final int realUserId = handleIncomingUser(userIdArg, "invalidateCachesForOverlay");
-            enforceActor(packageName, "invalidateCachesForOverlay", realUserId);
+            enforceActor(overlay, "invalidateCachesForOverlay", realUserId);
             final long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    mImpl.removeIdmapForOverlay(packageName, realUserId);
+                    try {
+                        mImpl.removeIdmapForOverlay(overlay, realUserId);
+                    } catch (OperationFailedException e) {
+                        Slog.w(TAG, "invalidate caches for overlay '" + overlay + "' failed", e);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -893,25 +871,63 @@
             }
         }
 
-        private Optional<PackageAndUser> executeRequest(
-                @NonNull final OverlayManagerTransaction.Request request) throws Exception {
-            final int realUserId = handleIncomingUser(request.userId, request.typeToString());
-            enforceActor(request.packageName, request.typeToString(), realUserId);
+        private Set<PackageAndUser> executeRequest(
+                @NonNull final OverlayManagerTransaction.Request request)
+                throws OperationFailedException {
+            Objects.requireNonNull(request, "Transaction contains a null request");
+            Objects.requireNonNull(request.overlay,
+                    "Transaction overlay identifier must be non-null");
+
+            final int callingUid = Binder.getCallingUid();
+            final int realUserId;
+            if (request.type == TYPE_REGISTER_FABRICATED
+                    || request.type == TYPE_UNREGISTER_FABRICATED) {
+                if (request.userId != UserHandle.USER_ALL) {
+                    throw new IllegalArgumentException(request.typeToString()
+                            + " unsupported for user " + request.userId);
+                }
+                realUserId = UserHandle.USER_ALL;
+
+                // Enforce that the calling process can only register and unregister fabricated
+                // overlays using its package name.
+                final String pkgName = request.overlay.getPackageName();
+                if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
+                        mPackageManager.getPackagesForUid(callingUid), pkgName)) {
+                    throw new IllegalArgumentException("UID " + callingUid + " does own package"
+                            + "name " + pkgName);
+                }
+            } else {
+                // Enforce actor requirements for enabling, disabling, and reordering overlays.
+                realUserId = handleIncomingUser(request.userId, request.typeToString());
+                enforceActor(request.overlay, request.typeToString(), realUserId);
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 switch (request.type) {
                     case TYPE_SET_ENABLED:
-                        Optional<PackageAndUser> opt1 =
-                                mImpl.setEnabled(request.packageName, true, request.userId);
-                        Optional<PackageAndUser> opt2 =
-                                mImpl.setHighestPriority(request.packageName, request.userId);
-                        // Both setEnabled and setHighestPriority affected the same
-                        // target package and user: if both return non-empty
-                        // Optionals, they are identical
-                        return opt1.isPresent() ? opt1 : opt2;
+                        Set<PackageAndUser> result = null;
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setEnabled(request.overlay, true, realUserId));
+                        result = CollectionUtils.addAll(result,
+                                mImpl.setHighestPriority(request.overlay, realUserId));
+                        return CollectionUtils.emptyIfNull(result);
+
                     case TYPE_SET_DISABLED:
-                        return mImpl.setEnabled(request.packageName, false, request.userId);
+                        return mImpl.setEnabled(request.overlay, false, realUserId);
+
+                    case TYPE_REGISTER_FABRICATED:
+                        final FabricatedOverlayInternal fabricated =
+                                request.extras.getParcelable(
+                                        OverlayManagerTransaction.Request.BUNDLE_FABRICATED_OVERLAY
+                                );
+                        Objects.requireNonNull(fabricated,
+                                "no fabricated overlay attached to request");
+                        return mImpl.registerFabricatedOverlay(fabricated);
+
+                    case TYPE_UNREGISTER_FABRICATED:
+                        return mImpl.unregisterFabricatedOverlay(request.overlay);
+
                     default:
                         throw new IllegalArgumentException("unsupported request: " + request);
                 }
@@ -921,7 +937,7 @@
         }
 
         private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
-                throws Exception {
+                throws OperationFailedException {
             if (DEBUG) {
                 Slog.d(TAG, "commit " + transaction);
             }
@@ -931,25 +947,25 @@
 
             // map: userId -> set<package-name>: target packages of overlays in
             // this transaction
-            SparseArray<Set<String>> transactionTargets = new SparseArray<>();
+            final SparseArray<Set<String>> transactionTargets = new SparseArray<>();
 
             // map: userId -> set<package-name>: packages that need to reload
             // their resources due to changes to the overlays in this
             // transaction
-            SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
+            final SparseArray<List<String>> affectedPackagesToUpdate = new SparseArray<>();
 
             synchronized (mLock) {
-
                 // execute the requests (as calling user)
                 for (final OverlayManagerTransaction.Request request : transaction) {
-                    executeRequest(request).ifPresent(target -> {
-                        Set<String> userTargets = transactionTargets.get(target.userId);
-                        if (userTargets == null) {
-                            userTargets = new ArraySet<String>();
-                            transactionTargets.put(target.userId, userTargets);
-                        }
-                        userTargets.add(target.packageName);
-                    });
+                    executeRequest(request).forEach(
+                            target -> {
+                                Set<String> userTargets = transactionTargets.get(target.userId);
+                                if (userTargets == null) {
+                                    userTargets = new ArraySet<>();
+                                    transactionTargets.put(target.userId, userTargets);
+                                }
+                                userTargets.add(target.packageName);
+                            });
                 }
 
                 // past the point of no return: the entire transaction has been
@@ -975,10 +991,7 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // schedule apps to refresh
-                    for (int index = 0; index < affectedPackagesToUpdate.size(); index++) {
-                        final int userId = affectedPackagesToUpdate.keyAt(index);
-                        updateActivityManager(affectedPackagesToUpdate.valueAt(index), userId);
-                    }
+                    updateActivityManager(affectedPackagesToUpdate);
 
                     // broadcast the ACTION_OVERLAY_CHANGED intents
                     for (int index = 0; index < transactionTargets.size(); index++) {
@@ -1059,12 +1072,12 @@
                         dumpState.setField(arg);
                         break;
                     default:
-                        dumpState.setPackageName(arg);
+                        dumpState.setOverlyIdentifier(arg);
                         break;
                 }
             }
             if (dumpState.getPackageName() == null && opti < args.length) {
-                dumpState.setPackageName(args[opti]);
+                dumpState.setOverlyIdentifier(args[opti]);
                 opti++;
             }
 
@@ -1101,12 +1114,12 @@
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
         }
 
-        private void enforceActor(String packageName, String methodName, int realUserId)
-                throws SecurityException {
-            OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, realUserId);
+        private void enforceActor(@NonNull OverlayIdentifier overlay, @NonNull String methodName,
+                int realUserId) throws SecurityException {
+            OverlayInfo overlayInfo = mImpl.getOverlayInfo(overlay, realUserId);
             if (overlayInfo == null) {
                 throw new IllegalArgumentException("Unable to retrieve overlay information for "
-                        + packageName);
+                        + overlay);
             }
 
             int callingUid = Binder.getCallingUid();
@@ -1115,7 +1128,13 @@
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-
+        private static class AndroidPackageUsers {
+            private AndroidPackage mPackage;
+            private final Set<Integer> mInstalledUsers = new ArraySet<>();
+            private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
+                this.mPackage = pkg;
+            }
+        }
         private final Context mContext;
         private final IPackageManager mPackageManager;
         private final PackageManagerInternal mPackageManagerInternal;
@@ -1125,7 +1144,8 @@
         // intent, querying the PackageManagerService for the actual current
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
-        private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
+        private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
+        private final Set<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
             mContext = context;
@@ -1133,29 +1153,112 @@
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
-        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
-                final boolean useCache) {
-            if (useCache) {
-                final PackageInfo cachedPi = getCachedPackageInfo(packageName, userId);
-                if (cachedPi != null) {
-                    return cachedPi;
+        /**
+         * Initializes the helper for the user. This only needs to be invoked one time before
+         * packages of this user are queried.
+         * @param userId the user id to initialize
+         * @return a map of package name to all packages installed in the user
+         */
+        @NonNull
+        public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
+            if (!mInitializedUsers.contains(userId)) {
+                mInitializedUsers.add(userId);
+                mPackageManagerInternal.forEachInstalledPackage(
+                        (pkg) -> addPackageUser(pkg, userId), userId);
+            }
+
+            final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                final AndroidPackageUsers pkg = mCache.valueAt(i);
+                if (pkg.mInstalledUsers.contains(userId)) {
+                    userPackages.put(mCache.keyAt(i), pkg.mPackage);
                 }
             }
-            try {
-                final PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0, userId);
-                if (useCache && pi != null) {
-                    cachePackageInfo(packageName, userId, pi);
-                }
-                return pi;
-            } catch (RemoteException e) {
-                // Intentionally left empty.
-            }
-            return null;
+            return userPackages;
         }
 
         @Override
-        public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
-            return getPackageInfo(packageName, userId, true);
+        @Nullable
+        public AndroidPackage getPackageForUser(@NonNull final String packageName,
+                final int userId) {
+            final AndroidPackageUsers pkg = mCache.get(packageName);
+            if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
+                return pkg.mPackage;
+            }
+            try {
+                if (!mPackageManager.isPackageAvailable(packageName, userId)) {
+                    return null;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to check availability of package '" + packageName
+                        + "' for user " + userId, e);
+                return null;
+            }
+            return addPackageUser(packageName, userId);
+        }
+
+        @NonNull
+        private AndroidPackage addPackageUser(@NonNull final String packageName,
+                final int user) {
+            final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
+            if (pkg == null) {
+                Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
+                        + " continuing as if package was never added", new Throwable());
+                return null;
+            }
+            return addPackageUser(pkg, user);
+        }
+
+        @NonNull
+        private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
+                final int user) {
+            AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
+            if (pkgUsers == null) {
+                pkgUsers = new AndroidPackageUsers(pkg);
+                mCache.put(pkg.getPackageName(), pkgUsers);
+            } else {
+                pkgUsers.mPackage = pkg;
+            }
+            pkgUsers.mInstalledUsers.add(user);
+            return pkgUsers.mPackage;
+        }
+
+
+        @NonNull
+        private void removePackageUser(@NonNull final String packageName, final int user) {
+            final AndroidPackageUsers pkgUsers = mCache.get(packageName);
+            if (pkgUsers == null) {
+                return;
+            }
+            removePackageUser(pkgUsers, user);
+        }
+
+        @NonNull
+        private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
+            pkg.mInstalledUsers.remove(user);
+            if (pkg.mInstalledUsers.isEmpty()) {
+                mCache.remove(pkg.mPackage.getPackageName());
+            }
+        }
+
+        @Nullable
+        public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
+            return addPackageUser(packageName, userId);
+        }
+
+        @Nullable
+        public AndroidPackage onPackageUpdated(@NonNull final String packageName,
+                final int userId) {
+            return addPackageUser(packageName, userId);
+        }
+
+        public void onPackageRemoved(@NonNull final String packageName, final int userId) {
+            removePackageUser(packageName, userId);
+        }
+
+        @Override
+        public boolean isInstantApp(@NonNull final String packageName, final int userId) {
+            return mPackageManagerInternal.isInstantApp(packageName, userId);
         }
 
         @NonNull
@@ -1179,15 +1282,6 @@
         }
 
         @Override
-        public List<PackageInfo> getOverlayPackages(final int userId) {
-            final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
-            for (final PackageInfo info : overlays) {
-                cachePackageInfo(info.packageName, userId, info);
-            }
-            return overlays;
-        }
-
-        @Override
         public String getConfigSignaturePackage() {
             final String[] pkgs = mPackageManagerInternal.getKnownPackageNames(
                     PackageManagerInternal.PACKAGE_OVERLAY_CONFIG_SIGNATURE,
@@ -1200,16 +1294,14 @@
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @NonNull String targetOverlayableName, int userId)
                 throws IOException {
-            PackageInfo packageInfo = getPackageInfo(packageName, userId);
+            final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
             if (packageInfo == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
                 return apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
@@ -1224,16 +1316,14 @@
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws IOException {
-            PackageInfo packageInfo = getPackageInfo(targetPackageName, userId);
+            AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
             if (packageInfo == null) {
                 throw new IOException("Unable to get target package");
             }
 
-            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
                 return apkAssets.definesOverlayable();
             } finally {
                 if (apkAssets != null) {
@@ -1250,35 +1340,10 @@
             mContext.enforceCallingOrSelfPermission(permission, message);
         }
 
-        public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
-                final int userId) {
-            final HashMap<String, PackageInfo> map = mCache.get(userId);
-            return map == null ? null : map.get(packageName);
-        }
-
-        public void cachePackageInfo(@NonNull final String packageName, final int userId,
-                @NonNull final PackageInfo pi) {
-            HashMap<String, PackageInfo> map = mCache.get(userId);
-            if (map == null) {
-                map = new HashMap<>();
-                mCache.put(userId, map);
-            }
-            map.put(packageName, pi);
-        }
-
-        public void forgetPackageInfo(@NonNull final String packageName, final int userId) {
-            final HashMap<String, PackageInfo> map = mCache.get(userId);
-            if (map == null) {
-                return;
-            }
-            map.remove(packageName);
-            if (map.isEmpty()) {
-                mCache.delete(userId);
-            }
-        }
-
         public void forgetAllPackageInfos(final int userId) {
-            mCache.delete(userId);
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                removePackageUser(mCache.valueAt(i), userId);
+            }
         }
 
         @Nullable
@@ -1292,19 +1357,12 @@
         }
 
         private static final String TAB1 = "    ";
-        private static final String TAB2 = TAB1 + TAB1;
 
         public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
-            pw.println("PackageInfo cache");
+            pw.println("AndroidPackage cache");
 
             if (!dumpState.isVerbose()) {
-                int count = 0;
-                final int n = mCache.size();
-                for (int i = 0; i < n; i++) {
-                    final int userId = mCache.keyAt(i);
-                    count += mCache.get(userId).size();
-                }
-                pw.println(TAB1 + count + " package(s)");
+                pw.println(TAB1 + mCache.size() + " package(s)");
                 return;
             }
 
@@ -1313,25 +1371,70 @@
                 return;
             }
 
-            final int n = mCache.size();
-            for (int i = 0; i < n; i++) {
-                final int userId = mCache.keyAt(i);
-                pw.println(TAB1 + "User " + userId);
-                final HashMap<String, PackageInfo> map = mCache.get(userId);
-                for (Map.Entry<String, PackageInfo> entry : map.entrySet()) {
-                    pw.println(TAB2 + entry.getKey() + ": " + entry.getValue());
-                }
+            for (int i = 0, n = mCache.size(); i < n; i++) {
+                final String packageName = mCache.keyAt(i);
+                final AndroidPackageUsers pkg = mCache.valueAt(i);
+                pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
+                pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
             }
         }
     }
 
+    private void updateTargetPackages(@Nullable PackageAndUser updatedTarget) {
+        if (updatedTarget != null) {
+            updateTargetPackages(Set.of(updatedTarget));
+        }
+    }
+
+    private void updateTargetPackages(@Nullable Set<PackageAndUser> updatedTargets) {
+        if (CollectionUtils.isEmpty(updatedTargets)) {
+            return;
+        }
+        persistSettings();
+        final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(updatedTargets);
+        FgThread.getHandler().post(() -> {
+            for (int i = 0, n = userTargets.size(); i < n; i++) {
+                final ArraySet<String> targets = userTargets.valueAt(i);
+                final int userId = userTargets.keyAt(i);
+
+                // Update the overlay paths in package manager.
+                final List<String> affectedPackages = updatePackageManager(targets, userId);
+                updateActivityManager(affectedPackages, userId);
+
+                // Overlays targeting shared libraries may cause more packages to need to be
+                // refreshed.
+                broadcastActionOverlayChanged(targets, userId);
+            }
+        });
+    }
+
+    @Nullable
+    private static SparseArray<ArraySet<String>> groupTargetsByUserId(
+            @Nullable final Set<PackageAndUser> targetsAndUsers) {
+        final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
+        CollectionUtils.forEach(targetsAndUsers, target -> {
+            ArraySet<String> targets = userTargets.get(target.userId);
+            if (targets == null) {
+                targets = new ArraySet<>();
+                userTargets.put(target.userId, targets);
+            }
+            targets.add(target.packageName);
+        });
+        return userTargets;
+    }
+
     // Helper methods to update other parts of the system or read/write
     // settings: these methods should never call into each other!
 
-    private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
+    private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
             final int userId) {
+        CollectionUtils.forEach(targetPackages,
+                target -> broadcastActionOverlayChanged(target, userId));
+    }
+
+    private static void broadcastActionOverlayChanged(String targetPackage, final int userId) {
         final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
-                Uri.fromParts("package", targetPackageName, null));
+                Uri.fromParts("package", targetPackage, null));
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         try {
             ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
@@ -1345,7 +1448,7 @@
      * Tell the activity manager to tell a set of packages to reload their
      * resources.
      */
-    private void updateActivityManager(List<String> targetPackageNames, final int userId) {
+    private void updateActivityManager(@NonNull List<String> targetPackageNames, final int userId) {
         final IActivityManager am = ActivityManager.getService();
         try {
             am.scheduleApplicationInfoChanged(targetPackageNames, userId);
@@ -1354,16 +1457,33 @@
         }
     }
 
-    private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) {
-        return updatePackageManager(Collections.singletonList(targetPackageNames), userId);
+    private void updateActivityManager(@NonNull SparseArray<List<String>> targetPackageNames) {
+        for (int i = 0, n = targetPackageNames.size(); i < n; i++) {
+            updateActivityManager(targetPackageNames.valueAt(i), targetPackageNames.keyAt(i));
+        }
+    }
+
+    @NonNull
+    private SparseArray<List<String>> updatePackageManager(@Nullable Set<PackageAndUser> targets) {
+        if (CollectionUtils.isEmpty(targets)) {
+            return new SparseArray<>();
+        }
+        final SparseArray<List<String>> affectedTargets = new SparseArray<>();
+        final SparseArray<ArraySet<String>> userTargets = groupTargetsByUserId(targets);
+        for (int i = 0, n = userTargets.size(); i < n; i++) {
+            final int userId = userTargets.keyAt(i);
+            affectedTargets.put(userId, updatePackageManager(userTargets.valueAt(i), userId));
+        }
+        return affectedTargets;
     }
 
     /**
      * Updates the target packages' set of enabled overlays in PackageManager.
      * @return the package names of affected targets (a superset of
-     *         targetPackageNames: the target themserlves and shared libraries)
+     *         targetPackageNames: the target themselves and shared libraries)
      */
-    private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
+    @NonNull
+    private List<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
             final int userId) {
         try {
             traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index c547c36..fb183f5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -28,26 +28,31 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.om.CriticalOverlayInfo;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.content.om.OverlayConfig;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Internal implementation of OverlayManagerService.
@@ -85,29 +90,42 @@
      * should either scrap the overlay manager's previous settings or merge the old
      * settings with the new.
      */
-    private boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
+    private boolean mustReinitializeOverlay(@NonNull final AndroidPackage theTruth,
             @Nullable final OverlayInfo oldSettings) {
         if (oldSettings == null) {
             return true;
         }
-        if (!Objects.equals(theTruth.overlayTarget, oldSettings.targetPackageName)) {
+        if (!Objects.equals(theTruth.getOverlayTarget(), oldSettings.targetPackageName)) {
             return true;
         }
-        if (!Objects.equals(theTruth.targetOverlayableName, oldSettings.targetOverlayableName)) {
+        if (!Objects.equals(theTruth.getOverlayTargetName(), oldSettings.targetOverlayableName)) {
             return true;
         }
-
-        boolean isMutable = isPackageConfiguredMutable(theTruth.packageName);
+        if (oldSettings.isFabricated) {
+            return true;
+        }
+        boolean isMutable = isPackageConfiguredMutable(theTruth);
         if (isMutable != oldSettings.isMutable) {
             return true;
         }
-
         // If an immutable overlay changes its configured enabled state, reinitialize the overlay.
-        if (!isMutable && isPackageConfiguredEnabled(theTruth.packageName)
-                != oldSettings.isEnabled()) {
+        if (!isMutable && isPackageConfiguredEnabled(theTruth) != oldSettings.isEnabled()) {
             return true;
         }
+        return false;
+    }
 
+    private boolean mustReinitializeOverlay(@NonNull final FabricatedOverlayInfo theTruth,
+            @Nullable final OverlayInfo oldSettings) {
+        if (oldSettings == null) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetPackageName, oldSettings.targetPackageName)) {
+            return true;
+        }
+        if (!Objects.equals(theTruth.targetOverlayable, oldSettings.targetOverlayableName)) {
+            return true;
+        }
         return false;
     }
 
@@ -129,87 +147,45 @@
      * of two sets: the set of targets with currently active overlays, and the
      * set of targets that had, but no longer have, active overlays.
      */
-    ArrayList<String> updateOverlaysForUser(final int newUserId) {
+    @NonNull
+    ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
         if (DEBUG) {
             Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
         }
 
-        final Set<String> packagesToUpdateAssets = new ArraySet<>();
-        final ArrayMap<String, List<OverlayInfo>> tmp = mSettings.getOverlaysForUser(newUserId);
-        final int tmpSize = tmp.size();
-        final ArrayMap<String, OverlayInfo> storedOverlayInfos = new ArrayMap<>(tmpSize);
-        for (int i = 0; i < tmpSize; i++) {
-            final List<OverlayInfo> chunk = tmp.valueAt(i);
-            final int chunkSize = chunk.size();
-            for (int j = 0; j < chunkSize; j++) {
-                final OverlayInfo oi = chunk.get(j);
-                storedOverlayInfos.put(oi.packageName, oi);
-            }
-        }
+        // Remove the settings of all overlays that are no longer installed for this user.
+        final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+        final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
+                newUserId);
+        CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
+                (info) -> !userPackages.containsKey(info.packageName), newUserId));
 
-        // Reset overlays if something critical like the target package name
-        // has changed
-        List<PackageInfo> overlayPackages = mPackageManager.getOverlayPackages(newUserId);
-        final int overlayPackagesSize = overlayPackages.size();
-        for (int i = 0; i < overlayPackagesSize; i++) {
-            final PackageInfo overlayPackage = overlayPackages.get(i);
-            final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
-
-            int priority = getPackageConfiguredPriority(overlayPackage.packageName);
-            if (mustReinitializeOverlay(overlayPackage, oi)) {
-                // if targetPackageName has changed the package that *used* to
-                // be the target must also update its assets
-                if (oi != null) {
-                    packagesToUpdateAssets.add(oi.targetPackageName);
-                }
-
-                mSettings.init(overlayPackage.packageName, newUserId,
-                        overlayPackage.overlayTarget,
-                        overlayPackage.targetOverlayableName,
-                        overlayPackage.applicationInfo.getBaseCodePath(),
-                        isPackageConfiguredMutable(overlayPackage.packageName),
-                        isPackageConfiguredEnabled(overlayPackage.packageName),
-                        priority, overlayPackage.overlayCategory);
-            } else if (priority != oi.priority) {
-                mSettings.setPriority(overlayPackage.packageName, newUserId, priority);
-                packagesToUpdateAssets.add(oi.targetPackageName);
-            }
-
-            storedOverlayInfos.remove(overlayPackage.packageName);
-        }
-
-        // any OverlayInfo left in storedOverlayInfos is no longer
-        // installed and should be removed
-        final int storedOverlayInfosSize = storedOverlayInfos.size();
-        for (int i = 0; i < storedOverlayInfosSize; i++) {
-            final OverlayInfo oi = storedOverlayInfos.valueAt(i);
-            mSettings.remove(oi.packageName, oi.userId);
-            removeIdmapIfPossible(oi);
-            packagesToUpdateAssets.add(oi.targetPackageName);
-        }
-
-        // make sure every overlay's state is up-to-date; this needs to happen
-        // after old overlays have been removed, or we risk removing a
-        // legitimate idmap file if a new overlay package has the same apk path
-        // as the removed overlay package used to have
-        for (int i = 0; i < overlayPackagesSize; i++) {
-            final PackageInfo overlayPackage = overlayPackages.get(i);
+        // Update the state of all installed packages containing overlays, and initialize new
+        // overlays that are not currently in the settings.
+        for (int i = 0, n = userPackages.size(); i < n; i++) {
+            final AndroidPackage pkg = userPackages.valueAt(i);
             try {
-                updateState(overlayPackage.overlayTarget, overlayPackage.packageName,
-                        newUserId, 0);
-            } catch (OverlayManagerSettings.BadKeyException e) {
-                Slog.e(TAG, "failed to update settings", e);
-                mSettings.remove(overlayPackage.packageName, newUserId);
+                CollectionUtils.addAll(updatedTargets,
+                        updatePackageOverlays(pkg, newUserId, 0 /* flags */));
+
+                // When a new user is switched to for the first time, package manager must be
+                // informed of the overlay paths for all packages installed in the user.
+                updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+            } catch (OperationFailedException e) {
+                Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
+                        + "' for user " + newUserId + "", e);
             }
-            packagesToUpdateAssets.add(overlayPackage.overlayTarget);
         }
 
-        // remove target packages that are not installed
-        final Iterator<String> iter = packagesToUpdateAssets.iterator();
-        while (iter.hasNext()) {
-            String targetPackageName = iter.next();
-            if (mPackageManager.getPackageInfo(targetPackageName, newUserId) == null) {
-                iter.remove();
+        // Update the state of all fabricated overlays, and initialize fabricated overlays in the
+        // new user.
+        for (final FabricatedOverlayInfo info : getFabricatedOverlayInfos()) {
+            try {
+                CollectionUtils.addAll(updatedTargets, registerFabricatedOverlay(
+                        info, newUserId));
+            } catch (OperationFailedException e) {
+                Slog.e(TAG, "failed to initialize fabricated overlay of '" + info.path
+                        + "' for user " + newUserId + "", e);
             }
         }
 
@@ -232,14 +208,20 @@
         // Enable the default overlay if its category does not have a single overlay enabled.
         for (final String defaultOverlay : mDefaultOverlays) {
             try {
-                final OverlayInfo oi = mSettings.getOverlayInfo(defaultOverlay, newUserId);
+                // OverlayConfig is the new preferred way to enable overlays by default. This legacy
+                // default enabled method was created before overlays could have a name specified.
+                // Only allow enabling overlays without a name using this mechanism.
+                final OverlayIdentifier overlay = new OverlayIdentifier(defaultOverlay);
+
+                final OverlayInfo oi = mSettings.getOverlayInfo(overlay, newUserId);
                 if (!enabledCategories.contains(oi.category)) {
                     Slog.w(TAG, "Enabling default overlay '" + defaultOverlay + "' for target '"
                             + oi.targetPackageName + "' in category '" + oi.category + "' for user "
                             + newUserId);
-                    mSettings.setEnabled(oi.packageName, newUserId, true);
-                    if (updateState(oi.targetPackageName, oi.packageName, newUserId, 0)) {
-                        packagesToUpdateAssets.add(oi.targetPackageName);
+                    mSettings.setEnabled(overlay, newUserId, true);
+                    if (updateState(oi, newUserId, 0)) {
+                        CollectionUtils.add(updatedTargets,
+                                new PackageAndUser(oi.targetPackageName, oi.userId));
                     }
                 }
             } catch (OverlayManagerSettings.BadKeyException e) {
@@ -248,7 +230,8 @@
             }
         }
 
-        return new ArrayList<>(packagesToUpdateAssets);
+        cleanStaleResourceCache();
+        return updatedTargets;
     }
 
     void onUserRemoved(final int userId) {
@@ -258,236 +241,151 @@
         mSettings.removeUser(userId);
     }
 
-    Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName,
+    @NonNull
+    Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
-    Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName,
+    @NonNull
+    Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
-    Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId="
-                    + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
-        }
-
-        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
-    }
-
-    /**
-     * Update the state of any overlays for this target.
-     */
-    private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget(
-            @NonNull final String targetPackageName, final int userId, final int flags)
+    @NonNull
+    Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
-        final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName,
-                userId);
+        return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+    }
 
-        // Update the state for any overlay that targets this package.
+    @NonNull
+    Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+            throws OperationFailedException {
+        return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
+    }
+
+    @NonNull
+    Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
+        }
+        // Update the state of all overlays that target this package.
+        final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+
+        // Remove all the overlays this package declares.
+        return CollectionUtils.addAll(targets,
+                removeOverlaysForUser(oi -> pkgName.equals(oi.packageName), userId));
+    }
+
+    @NonNull
+    private Set<PackageAndUser> removeOverlaysForUser(
+            @NonNull final Predicate<OverlayInfo> condition, final int userId) {
+        final List<OverlayInfo> overlays = mSettings.removeIf(
+                io -> userId == io.userId && condition.test(io) );
+        Set<PackageAndUser> targets = Collections.emptySet();
+        for (int i = 0, n = overlays.size(); i < n; i++) {
+            final OverlayInfo info = overlays.get(i);
+            targets = CollectionUtils.add(targets,
+                    new PackageAndUser(info.targetPackageName, userId));
+
+            // Remove the idmap if the overlay is no longer installed for any user.
+            removeIdmapIfPossible(info);
+        }
+        return targets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+            final int userId, final int flags) {
         boolean modified = false;
-        for (final OverlayInfo oi : targetOverlays) {
-            final PackageInfo overlayPackage = mPackageManager.getPackageInfo(oi.packageName,
-                    userId);
-            if (overlayPackage == null) {
-                modified |= mSettings.remove(oi.packageName, oi.userId);
-                removeIdmapIfPossible(oi);
-            } else {
-                try {
-                    modified |= updateState(targetPackageName, oi.packageName, userId, flags);
-                } catch (OverlayManagerSettings.BadKeyException e) {
-                    Slog.e(TAG, "failed to update settings", e);
-                    modified |= mSettings.remove(oi.packageName, userId);
-                }
+        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
+        for (int i = 0, n = overlays.size(); i < n; i++) {
+            final OverlayInfo oi = overlays.get(i);
+            try {
+                modified |= updateState(oi, userId, flags);
+            } catch (OverlayManagerSettings.BadKeyException e) {
+                Slog.e(TAG, "failed to update settings", e);
+                modified |= mSettings.remove(oi.getOverlayIdentifier(), userId);
             }
         }
-
         if (!modified) {
-            // Update the overlay paths of the target within package manager if necessary.
-            final List<String> enabledOverlayPaths = new ArrayList<>(targetOverlays.size());
-
-            // Framework overlays are first in the overlay paths of a package within PackageManager.
-            for (final OverlayInfo oi : mSettings.getOverlaysForTarget("android", userId)) {
-                if (oi.isEnabled()) {
-                    enabledOverlayPaths.add(oi.baseCodePath);
-                }
-            }
-
-            for (final OverlayInfo oi : targetOverlays) {
-                if (oi.isEnabled()) {
-                    enabledOverlayPaths.add(oi.baseCodePath);
-                }
-            }
-
-            // TODO(): Use getEnabledOverlayPaths(userId, targetPackageName) instead of
-            // resourceDirs if in the future resourceDirs contains APKs other than overlays
-            PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, userId);
-            ApplicationInfo appInfo = packageInfo == null ? null : packageInfo.applicationInfo;
-            String[] resourceDirs = appInfo == null ? null : appInfo.resourceDirs;
-
-            // If the lists aren't the same length, the enabled overlays have changed
-            if (ArrayUtils.size(resourceDirs) != enabledOverlayPaths.size()) {
-                modified = true;
-            } else if (resourceDirs != null) {
-                // If any element isn't equal, an overlay or the order of overlays has changed
-                for (int index = 0; index < resourceDirs.length; index++) {
-                    if (!resourceDirs[index].equals(enabledOverlayPaths.get(index))) {
-                        modified = true;
-                        break;
-                    }
-                }
-            }
+            return Collections.emptySet();
         }
-
-        if (modified) {
-            return Optional.of(new PackageAndUser(targetPackageName, userId));
-        }
-        return Optional.empty();
+        return Set.of(new PackageAndUser(targetPackage, userId));
     }
 
-    Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
+    @NonNull
+    private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+            final int userId, final int flags) throws OperationFailedException {
+        if (pkg.getOverlayTarget() == null) {
+            // This package does not have overlays declared in its manifest.
+            return Collections.emptySet();
         }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
-            return onOverlayPackageRemoved(packageName, userId);
-        }
-
-        mSettings.init(packageName, userId, overlayPackage.overlayTarget,
-                overlayPackage.targetOverlayableName,
-                overlayPackage.applicationInfo.getBaseCodePath(),
-                isPackageConfiguredMutable(overlayPackage.packageName),
-                isPackageConfiguredEnabled(overlayPackage.packageName),
-                getPackageConfiguredPriority(overlayPackage.packageName),
-                overlayPackage.overlayCategory);
+        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
+        final int priority = getPackageConfiguredPriority(pkg);
         try {
-            if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            mSettings.remove(packageName, userId);
-            throw new OperationFailedException("failed to update settings", e);
-        }
-    }
+            OverlayInfo currentInfo = mSettings.getNullableOverlayInfo(overlay, userId);
+            if (mustReinitializeOverlay(pkg, currentInfo)) {
+                if (currentInfo != null) {
+                    // If the targetPackageName has changed, the package that *used* to
+                    // be the target must also update its assets.
+                    updatedTargets = CollectionUtils.add(updatedTargets,
+                            new PackageAndUser(currentInfo.targetPackageName, userId));
+                }
 
-    Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
-        }
-
-        try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            if (updateState(oi.targetPackageName, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+                currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
+                        pkg.getOverlayTargetName(), pkg.getBaseApkPath(),
+                        isPackageConfiguredMutable(pkg),
+                        isPackageConfiguredEnabled(pkg),
+                        getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(),
+                        false);
+            } else if (priority != currentInfo.priority) {
+                // Changing the priority of an overlay does not cause its settings to be
+                // reinitialized. Reorder the overlay and update its target package.
+                mSettings.setPriority(overlay, userId, priority);
+                updatedTargets = CollectionUtils.add(updatedTargets,
+                        new PackageAndUser(currentInfo.targetPackageName, userId));
             }
-            return Optional.empty();
+
+            // Update the enabled state of the overlay.
+            if (updateState(currentInfo, userId, flags)) {
+                updatedTargets = CollectionUtils.add(updatedTargets,
+                        new PackageAndUser(currentInfo.targetPackageName, userId));
+            }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
+    @NonNull
+    private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+            final int userId, final int flags) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId="
-                    + userId);
+            Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
         }
 
-        try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            if (updateState(oi.targetPackageName, packageName, userId,
-                        FLAG_OVERLAY_IS_BEING_REPLACED)) {
-                removeIdmapIfPossible(oi);
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to update settings", e);
-        }
-    }
+        // Update the state of overlays that target this package.
+        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        updatedTargets = CollectionUtils.addAll(updatedTargets,
+                updateOverlaysForTarget(pkgName, userId, flags));
 
-    Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId="
-                    + userId);
-        }
-
-        final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
+        // Realign the overlay settings with PackageManager's view of the package.
+        final AndroidPackage pkg = mPackageManager.getPackageForUser(pkgName, userId);
         if (pkg == null) {
-            Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found");
-            return onOverlayPackageRemoved(packageName, userId);
+            return onPackageRemoved(pkgName, userId);
         }
 
-        try {
-            final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
-            if (mustReinitializeOverlay(pkg, oldOi)) {
-                mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
-                        pkg.applicationInfo.getBaseCodePath(),
-                        isPackageConfiguredMutable(pkg.packageName),
-                        isPackageConfiguredEnabled(pkg.packageName),
-                        getPackageConfiguredPriority(pkg.packageName), pkg.overlayCategory);
-            }
-
-            if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
-                return Optional.of(new PackageAndUser(pkg.overlayTarget, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to update settings", e);
-        }
+        // Update the state of the overlays this package declares in its manifest.
+        updatedTargets = CollectionUtils.addAll(updatedTargets,
+                updatePackageOverlays(pkg, userId, flags));
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName,
-            final int userId) throws OperationFailedException {
-        try {
-            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
-            if (mSettings.remove(packageName, userId)) {
-                removeIdmapIfPossible(overlayInfo);
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
-            }
-            return Optional.empty();
-        } catch (OverlayManagerSettings.BadKeyException e) {
-            throw new OperationFailedException("failed to remove overlay", e);
-        }
-    }
-
-    OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
+    OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier packageName, final int userId) {
         try {
             return mSettings.getOverlayInfo(packageName, userId);
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -504,94 +402,78 @@
         return mSettings.getOverlaysForUser(userId);
     }
 
-    Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable,
-            final int userId) throws OperationFailedException {
+    @NonNull
+    Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+            final boolean enable, final int userId) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
-                        packageName, enable, userId));
-        }
-
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(
-                    String.format("failed to find overlay package %s for user %d",
-                        packageName, userId));
+            Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
+                    overlay, enable, userId));
         }
 
         try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+            final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
             if (!oi.isMutable) {
                 // Ignore immutable overlays.
                 throw new OperationFailedException(
                         "cannot enable immutable overlay packages in runtime");
             }
 
-            boolean modified = mSettings.setEnabled(packageName, userId, enable);
-            modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
+            boolean modified = mSettings.setEnabled(overlay, userId, enable);
+            modified |= updateState(oi, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
             }
-            return Optional.empty();
+            return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName,
+    Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
             boolean withinCategory, final int userId) throws OperationFailedException {
         if (DEBUG) {
-            Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
-                    + " withinCategory=%s userId=%d", packageName, withinCategory, userId));
-        }
-
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
+                    + " withinCategory=%s userId=%d", overlay, withinCategory, userId));
         }
 
         try {
-            final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-            final String targetPackageName = oi.targetPackageName;
+            final OverlayInfo enabledInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!enabledInfo.isMutable) {
+                throw new OperationFailedException(
+                        "cannot enable immutable overlay packages in runtime");
+            }
 
-            List<OverlayInfo> allOverlays = getOverlayInfosForTarget(targetPackageName, userId);
+            // Remove the overlay to have enabled from the list of overlays to disable.
+            List<OverlayInfo> allOverlays = getOverlayInfosForTarget(enabledInfo.targetPackageName,
+                    userId);
+            allOverlays.remove(enabledInfo);
 
             boolean modified = false;
-
-            // Disable all other overlays.
-            allOverlays.remove(oi);
             for (int i = 0; i < allOverlays.size(); i++) {
                 final OverlayInfo disabledInfo = allOverlays.get(i);
-                final String disabledOverlayPackageName = disabledInfo.packageName;
-                final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
-                        disabledOverlayPackageName, userId);
-                if (disabledOverlayPackageInfo == null) {
-                    modified |= mSettings.remove(disabledOverlayPackageName, userId);
-                    continue;
-                }
-
+                final OverlayIdentifier disabledOverlay = disabledInfo.getOverlayIdentifier();
                 if (!disabledInfo.isMutable) {
                     // Don't touch immutable overlays.
                     continue;
                 }
-                if (withinCategory && !Objects.equals(disabledOverlayPackageInfo.overlayCategory,
-                        oi.category)) {
+                if (withinCategory && !Objects.equals(disabledInfo.category,
+                        enabledInfo.category)) {
                     // Don't touch overlays from other categories.
                     continue;
                 }
 
                 // Disable the overlay.
-                modified |= mSettings.setEnabled(disabledOverlayPackageName, userId, false);
-                modified |= updateState(targetPackageName, disabledOverlayPackageName, userId, 0);
+                modified |= mSettings.setEnabled(disabledOverlay, userId, false);
+                modified |= updateState(disabledInfo, userId, 0);
             }
 
             // Enable the selected overlay.
-            modified |= mSettings.setEnabled(packageName, userId, true);
-            modified |= updateState(targetPackageName, packageName, userId, 0);
+            modified |= mSettings.setEnabled(overlay, userId, true);
+            modified |= updateState(enabledInfo, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(targetPackageName, userId));
+                return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -599,87 +481,200 @@
         }
     }
 
-    private boolean isPackageConfiguredMutable(@NonNull final String packageName) {
-        return mOverlayConfig.isMutable(packageName);
-    }
-
-    private int getPackageConfiguredPriority(@NonNull final String packageName) {
-        return mOverlayConfig.getPriority(packageName);
-    }
-
-    private boolean isPackageConfiguredEnabled(@NonNull final String packageName) {
-        return mOverlayConfig.isEnabled(packageName);
-    }
-
-    Optional<PackageAndUser> setPriority(@NonNull final String packageName,
-            @NonNull final String newParentPackageName, final int userId)
+    @NonNull
+    Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInternal overlay)
             throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
-                    + newParentPackageName + " userId=" + userId);
+        if (ParsingPackageUtils.validateName(overlay.overlayName,
+                false /* requireSeparator */, true /* requireFilename */) != null) {
+            throw new OperationFailedException(
+                    "overlay name can only consist of alphanumeric characters, '_', and '.'");
         }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
+        final FabricatedOverlayInfo info = mIdmapManager.createFabricatedOverlay(overlay);
+        if (info == null) {
+            throw new OperationFailedException("failed to create fabricated overlay");
         }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(registerFabricatedOverlay(info, userId));
         }
-
-        if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
+        return updatedTargets;
     }
 
-    Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName,
+    @NonNull
+    private Set<PackageAndUser> registerFabricatedOverlay(
+            @NonNull final FabricatedOverlayInfo info, int userId)
+            throws OperationFailedException {
+        final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
+                info.packageName, info.overlayName);
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
+        if (oi != null) {
+            if (!oi.isFabricated) {
+                throw new OperationFailedException("non-fabricated overlay with name '" +
+                        oi.overlayName + "' already present in '" + oi.packageName + "'");
+            }
+        }
+        try {
+            if (mustReinitializeOverlay(info, oi)) {
+                if (oi != null) {
+                    // If the fabricated overlay changes its target package, update the previous
+                    // target package so it no longer is overlaid.
+                    updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                }
+                oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
+                        info.targetOverlayable, info.path, true, false,
+                        OverlayConfig.DEFAULT_PRIORITY, null, true);
+            } else {
+                // The only non-critical part of the info that will change is path to the fabricated
+                // overlay.
+                mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
+            }
+            if (updateState(oi, userId, 0)) {
+                updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
+
+        return updatedTargets;
+    }
+
+    @NonNull
+    Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        for (int userId : mSettings.getUsers()) {
+            updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
+        }
+        return updatedTargets;
+    }
+
+    @NonNull
+    private Set<PackageAndUser> unregisterFabricatedOverlay(
+            @NonNull final OverlayIdentifier overlay, int userId) {
+        final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
+        if (oi != null) {
+            mSettings.remove(overlay, userId);
+            if (oi.isEnabled()) {
+                // Removing a fabricated overlay only changes the overlay path of a package if it is
+                // currently enabled.
+                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+            }
+        }
+        return Set.of();
+    }
+
+
+    private void cleanStaleResourceCache() {
+        // Clean up fabricated overlays that are no longer registered in any user.
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        for (final FabricatedOverlayInfo info : mIdmapManager.getFabricatedOverlayInfos()) {
+            if (!fabricatedPaths.contains(info.path)) {
+                mIdmapManager.deleteFabricatedOverlay(info.path);
+            }
+        }
+    }
+
+    /**
+     * Retrieves information about the fabricated overlays still in use.
+     * @return
+     */
+    @NonNull
+    private List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+        final Set<String> fabricatedPaths = mSettings.getAllBaseCodePaths();
+        // Filter out stale fabricated overlays.
+        final ArrayList<FabricatedOverlayInfo> infos = new ArrayList<>(
+                mIdmapManager.getFabricatedOverlayInfos());
+        infos.removeIf(info -> !fabricatedPaths.contains(info.path));
+        return infos;
+    }
+
+    private boolean isPackageConfiguredMutable(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.isMutable(overlay.getPackageName());
+    }
+
+    private int getPackageConfiguredPriority(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.getPriority(overlay.getPackageName());
+    }
+
+    private boolean isPackageConfiguredEnabled(@NonNull final AndroidPackage overlay) {
+        // TODO(162841629): Support overlay name in OverlayConfig
+        return mOverlayConfig.isEnabled(overlay.getPackageName());
+    }
+
+    Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+            @NonNull final OverlayIdentifier newParentOverlay, final int userId)
+            throws OperationFailedException {
+        try {
+            if (DEBUG) {
+                Slog.d(TAG, "setPriority overlay=" + overlay + " newParentOverlay="
+                        + newParentOverlay + " userId=" + userId);
+            }
+
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
+
+            if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
+                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Optional.empty();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
+    }
+
+    Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
-        }
+        try{
+            if (DEBUG) {
+                Slog.d(TAG, "setHighestPriority overlay=" + overlay + " userId=" + userId);
+            }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
-        }
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            if (mSettings.setHighestPriority(overlay, userId)) {
+                return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Set.of();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
         }
-
-        if (mSettings.setHighestPriority(packageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
     }
 
-    Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId)
-            throws OperationFailedException {
-        if (DEBUG) {
-            Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
-        }
+    Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+            final int userId) throws OperationFailedException {
+        try{
+            if (DEBUG) {
+                Slog.d(TAG, "setLowestPriority packageName=" + overlay + " userId=" + userId);
+            }
 
-        if (!isPackageConfiguredMutable(packageName)) {
-            throw new OperationFailedException(String.format(
-                        "overlay package %s user %d is not updatable", packageName, userId));
-        }
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(overlay, userId);
+            if (!overlayInfo.isMutable) {
+                // Ignore immutable overlays.
+                throw new OperationFailedException(
+                        "cannot change priority of an immutable overlay package at runtime");
+            }
 
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null) {
-            throw new OperationFailedException(String.format(
-                        "failed to find overlay package %s for user %d", packageName, userId));
+            if (mSettings.setLowestPriority(overlay, userId)) {
+                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+            }
+            return Optional.empty();
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
         }
-
-        if (mSettings.setLowestPriority(packageName, userId)) {
-            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
-        }
-        return Optional.empty();
     }
 
     void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
@@ -693,9 +688,14 @@
         return mDefaultOverlays;
     }
 
-    void removeIdmapForOverlay(String packageName, int userId) {
-        final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
-        removeIdmapIfPossible(oi);
+    void removeIdmapForOverlay(OverlayIdentifier overlay, int userId)
+            throws OperationFailedException {
+        try {
+            final OverlayInfo oi = mSettings.getOverlayInfo(overlay, userId);
+            removeIdmapIfPossible(oi);
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            throw new OperationFailedException("failed to update settings", e);
+        }
     }
 
     OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
@@ -709,7 +709,11 @@
             if (!oi.isEnabled()) {
                 continue;
             }
-            paths.addApkPath(oi.baseCodePath);
+            if (oi.isFabricated()) {
+                paths.addNonApkPath(oi.baseCodePath);
+            } else {
+                paths.addApkPath(oi.baseCodePath);
+            }
         }
         return paths.build();
     }
@@ -717,49 +721,53 @@
     /**
      * Returns true if the settings/state was modified, false otherwise.
      */
-    private boolean updateState(@NonNull final String targetPackageName,
-            @NonNull final String overlayPackageName, final int userId, final int flags)
-            throws OverlayManagerSettings.BadKeyException {
+    private boolean updateState(@NonNull final CriticalOverlayInfo info,
+            final int userId, final int flags) throws OverlayManagerSettings.BadKeyException {
+        final OverlayIdentifier overlay = info.getOverlayIdentifier();
+        final AndroidPackage targetPackage = mPackageManager.getPackageForUser(
+                info.getTargetPackageName(), userId);
+        final AndroidPackage overlayPackage = mPackageManager.getPackageForUser(
+                info.getPackageName(), userId);
 
-        final PackageInfo targetPackage = mPackageManager.getPackageInfo(targetPackageName, userId);
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
-                userId);
+        boolean modified = false;
+        if (overlayPackage == null) {
+            removeIdmapIfPossible(mSettings.getOverlayInfo(overlay, userId));
+            return mSettings.remove(overlay, userId);
+        }
+
+        modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
+        if (!info.isFabricated()) {
+            modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
+        }
 
         // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
         // layers.
-        boolean modified = false;
-        if (targetPackage != null && overlayPackage != null
-                && !("android".equals(targetPackageName)
-                    && !isPackageConfiguredMutable(overlayPackageName))) {
-            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+        final OverlayInfo updatedOverlayInfo = mSettings.getOverlayInfo(overlay, userId);
+        if (targetPackage != null && !("android".equals(info.getTargetPackageName())
+                && !isPackageConfiguredMutable(overlayPackage))) {
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage,
+                    updatedOverlayInfo.baseCodePath, overlay.getOverlayName(), userId);
         }
 
-        if (overlayPackage != null) {
-            modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
-                    overlayPackage.applicationInfo.getBaseCodePath());
-            modified |= mSettings.setCategory(overlayPackageName, userId,
-                    overlayPackage.overlayCategory);
-        }
-
-        final @OverlayInfo.State int currentState = mSettings.getState(overlayPackageName, userId);
-        final @OverlayInfo.State int newState = calculateNewState(targetPackage, overlayPackage,
+        final @OverlayInfo.State int currentState = mSettings.getState(overlay, userId);
+        final @OverlayInfo.State int newState = calculateNewState(updatedOverlayInfo, targetPackage,
                 userId, flags);
         if (currentState != newState) {
             if (DEBUG) {
                 Slog.d(TAG, String.format("%s:%d: %s -> %s",
-                        overlayPackageName, userId,
+                        overlay, userId,
                         OverlayInfo.stateToString(currentState),
                         OverlayInfo.stateToString(newState)));
             }
-            modified |= mSettings.setState(overlayPackageName, userId, newState);
+            modified |= mSettings.setState(overlay, userId, newState);
         }
+
         return modified;
     }
 
-    private @OverlayInfo.State int calculateNewState(@Nullable final PackageInfo targetPackage,
-            @Nullable final PackageInfo overlayPackage, final int userId, final int flags)
+    private @OverlayInfo.State int calculateNewState(@NonNull final OverlayInfo info,
+            @Nullable final AndroidPackage targetPackage, final int userId, final int flags)
             throws OverlayManagerSettings.BadKeyException {
-
         if ((flags & FLAG_TARGET_IS_BEING_REPLACED) != 0) {
             return STATE_TARGET_IS_BEING_REPLACED;
         }
@@ -768,20 +776,15 @@
             return STATE_OVERLAY_IS_BEING_REPLACED;
         }
 
-        // assert expectation on overlay package: can only be null if the flags are used
-        if (DEBUG && overlayPackage == null) {
-            throw new IllegalArgumentException("null overlay package not compatible with no flags");
-        }
-
         if (targetPackage == null) {
             return STATE_MISSING_TARGET;
         }
 
-        if (!mIdmapManager.idmapExists(overlayPackage, userId)) {
+        if (!mIdmapManager.idmapExists(info)) {
             return STATE_NO_IDMAP;
         }
 
-        final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
+        final boolean enabled = mSettings.getEnabled(info.getOverlayIdentifier(), userId);
         return enabled ? STATE_ENABLED : STATE_DISABLED;
     }
 
@@ -810,7 +813,7 @@
         final int[] userIds = mSettings.getUsers();
         for (int userId : userIds) {
             try {
-                final OverlayInfo tmp = mSettings.getOverlayInfo(oi.packageName, userId);
+                final OverlayInfo tmp = mSettings.getOverlayInfo(oi.getOverlayIdentifier(), userId);
                 if (tmp != null && tmp.isEnabled()) {
                     // someone is still using the idmap file -> we cannot remove it
                     return;
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 0613dff..e3e0906 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -21,31 +21,32 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 /**
@@ -68,34 +69,45 @@
      */
     private final ArrayList<SettingsItem> mItems = new ArrayList<>();
 
-    void init(@NonNull final String packageName, final int userId,
+    @NonNull
+    OverlayInfo init(@NonNull final OverlayIdentifier overlay, final int userId,
             @NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
             @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
-            @Nullable String overlayCategory) {
-        remove(packageName, userId);
-        insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
-                overlayCategory));
+            @Nullable String overlayCategory, boolean isFabricated) {
+        remove(overlay, userId);
+        final SettingsItem item = new SettingsItem(overlay, userId, targetPackageName,
+                targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled,
+                isMutable, priority, overlayCategory, isFabricated);
+        insert(item);
+        return item.getOverlayInfo();
     }
 
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean remove(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean remove(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
             return false;
         }
-
         mItems.remove(idx);
         return true;
     }
 
-    @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+    @NonNull OverlayInfo getOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId)
             throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
+        }
+        return mItems.get(idx).getOverlayInfo();
+    }
+
+    @Nullable
+    OverlayInfo getNullableOverlayInfo(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
+        if (idx < 0) {
+            return null;
         }
         return mItems.get(idx).getOverlayInfo();
     }
@@ -103,28 +115,29 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setBaseCodePath(@NonNull final String packageName, final int userId,
+    boolean setBaseCodePath(@NonNull final OverlayIdentifier overlay, final int userId,
             @NonNull final String path) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setBaseCodePath(path);
     }
 
-    boolean setCategory(@NonNull final String packageName, final int userId,
+    boolean setCategory(@NonNull final OverlayIdentifier overlay, final int userId,
             @Nullable String category) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setCategory(category);
     }
 
-    boolean getEnabled(@NonNull final String packageName, final int userId) throws BadKeyException {
-        final int idx = select(packageName, userId);
+    boolean getEnabled(@NonNull final OverlayIdentifier overlay, final int userId)
+            throws BadKeyException {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).isEnabled();
     }
@@ -132,20 +145,20 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setEnabled(@NonNull final String packageName, final int userId, final boolean enable)
-            throws BadKeyException {
-        final int idx = select(packageName, userId);
+    boolean setEnabled(@NonNull final OverlayIdentifier overlay, final int userId,
+            final boolean enable) throws BadKeyException {
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setEnabled(enable);
     }
 
-    @OverlayInfo.State int getState(@NonNull final String packageName, final int userId)
+    @OverlayInfo.State int getState(@NonNull final OverlayIdentifier overlay, final int userId)
             throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).getState();
     }
@@ -153,11 +166,11 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setState(@NonNull final String packageName, final int userId,
+    boolean setState(@NonNull final OverlayIdentifier overlay, final int userId,
             final @OverlayInfo.State int state) throws BadKeyException {
-        final int idx = select(packageName, userId);
+        final int idx = select(overlay, userId);
         if (idx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
         return mItems.get(idx).setState(state);
     }
@@ -166,53 +179,82 @@
             final int userId) {
         // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
-        return selectWhereTarget(targetPackageName, userId)
-                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
-                .map(SettingsItem::getOverlayInfo)
-                .collect(Collectors.toList());
+        final List<SettingsItem> items = selectWhereTarget(targetPackageName, userId);
+        items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+        return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
     }
 
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
         // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
-        return selectWhereUser(userId)
-                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
-                .map(SettingsItem::getOverlayInfo)
-                .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
-                        Collectors.toList()));
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(OverlayManagerSettings::isImmutableFrameworkOverlay);
+
+        final ArrayMap<String, List<OverlayInfo>> targetInfos = new ArrayMap<>();
+        for (int i = 0, n = items.size(); i < n; i++) {
+            final SettingsItem item = items.get(i);
+            targetInfos.computeIfAbsent(item.mTargetPackageName, (String) -> new ArrayList<>())
+                    .add(item.getOverlayInfo());
+        }
+        return targetInfos;
+    }
+
+    Set<String> getAllBaseCodePaths() {
+        final Set<String> paths = new ArraySet<>();
+        mItems.forEach(item -> paths.add(item.mBaseCodePath));
+        return paths;
+    }
+
+    @NonNull
+    List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
+        return removeIf(info -> (predicate.test(info) && info.userId == userId));
+    }
+
+    @NonNull
+    List<OverlayInfo> removeIf(final @NonNull Predicate<OverlayInfo> predicate) {
+        List<OverlayInfo> removed = null;
+        for (int i = mItems.size() - 1; i >= 0; i--) {
+            final OverlayInfo info = mItems.get(i).getOverlayInfo();
+            if (predicate.test(info)) {
+                mItems.remove(i);
+                removed = CollectionUtils.add(removed, info);
+            }
+        }
+        return CollectionUtils.emptyIfNull(removed);
     }
 
     int[] getUsers() {
         return mItems.stream().mapToInt(SettingsItem::getUserId).distinct().toArray();
     }
 
+    private static boolean isImmutableFrameworkOverlay(@NonNull SettingsItem item) {
+        return !item.isMutable() && "android".equals(item.getTargetPackageName());
+    }
+
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
     boolean removeUser(final int userId) {
-        boolean removed = false;
-        for (int i = 0; i < mItems.size(); i++) {
-            final SettingsItem item = mItems.get(i);
+        return mItems.removeIf(item -> {
             if (item.getUserId() == userId) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Removing overlay " + item.mPackageName + " for user " + userId
+                    Slog.d(TAG, "Removing overlay " + item.mOverlay + " for user " + userId
                             + " from settings because user was removed");
                 }
-                mItems.remove(i);
-                removed = true;
-                i--;
+                return true;
             }
-        }
-        return removed;
+            return false;
+        });
     }
 
     /**
      * Reassigns the priority of an overlay maintaining the values of the overlays other settings.
      */
-    void setPriority(@NonNull final String packageName, final int userId, final int priority) {
-        final int moveIdx = select(packageName, userId);
+    void setPriority(@NonNull final OverlayIdentifier overlay, final int userId,
+            final int priority) throws BadKeyException {
+        final int moveIdx = select(overlay, userId);
         if (moveIdx < 0) {
-            throw new BadKeyException(packageName, userId);
+            throw new BadKeyException(overlay, userId);
         }
 
         final SettingsItem itemToMove = mItems.get(moveIdx);
@@ -224,17 +266,17 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setPriority(@NonNull final String packageName,
-            @NonNull final String newParentPackageName, final int userId) {
-        if (packageName.equals(newParentPackageName)) {
+    boolean setPriority(@NonNull final OverlayIdentifier overlay,
+            @NonNull final OverlayIdentifier newOverlay, final int userId) {
+        if (overlay.equals(newOverlay)) {
             return false;
         }
-        final int moveIdx = select(packageName, userId);
+        final int moveIdx = select(overlay, userId);
         if (moveIdx < 0) {
             return false;
         }
 
-        final int parentIdx = select(newParentPackageName, userId);
+        final int parentIdx = select(newOverlay, userId);
         if (parentIdx < 0) {
             return false;
         }
@@ -248,7 +290,7 @@
         }
 
         mItems.remove(moveIdx);
-        final int newParentIdx = select(newParentPackageName, userId) + 1;
+        final int newParentIdx = select(newOverlay, userId) + 1;
         mItems.add(newParentIdx, itemToMove);
         return moveIdx != newParentIdx;
     }
@@ -256,8 +298,8 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setLowestPriority(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
         if (idx <= 0) {
             // If the item doesn't exist or is already the lowest, don't change anything.
             return false;
@@ -272,8 +314,8 @@
     /**
      * Returns true if the settings were modified, false if they remain the same.
      */
-    boolean setHighestPriority(@NonNull final String packageName, final int userId) {
-        final int idx = select(packageName, userId);
+    boolean setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) {
+        final int idx = select(overlay, userId);
 
         // If the item doesn't exist or is already the highest, don't change anything.
         if (idx < 0 || idx == mItems.size() - 1) {
@@ -297,7 +339,6 @@
                 break;
             }
         }
-
         mItems.add(i + 1, item);
     }
 
@@ -308,7 +349,12 @@
             items = items.filter(item -> item.mUserId == dumpState.getUserId());
         }
         if (dumpState.getPackageName() != null) {
-            items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName()));
+            items = items.filter(item -> item.mOverlay.getPackageName()
+                    .equals(dumpState.getPackageName()));
+        }
+        if (dumpState.getOverlayName() != null) {
+            items = items.filter(item -> item.mOverlay.getOverlayName()
+                    .equals(dumpState.getOverlayName()));
         }
 
         // display items
@@ -322,10 +368,11 @@
 
     private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw,
             @NonNull final SettingsItem item) {
-        pw.println(item.mPackageName + ":" + item.getUserId() + " {");
+        pw.println(item.mOverlay + ":" + item.getUserId() + " {");
         pw.increaseIndent();
 
-        pw.println("mPackageName...........: " + item.mPackageName);
+        pw.println("mPackageName...........: " + item.mOverlay.getPackageName());
+        pw.println("mOverlayName...........: " + item.mOverlay.getOverlayName());
         pw.println("mUserId................: " + item.getUserId());
         pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
         pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
@@ -335,6 +382,7 @@
         pw.println("mIsMutable.............: " + item.isMutable());
         pw.println("mPriority..............: " + item.mPriority);
         pw.println("mCategory..............: " + item.mCategory);
+        pw.println("mIsFabricated..........: " + item.mIsFabricated);
 
         pw.decreaseIndent();
         pw.println("}");
@@ -344,7 +392,10 @@
             @NonNull final SettingsItem item, @NonNull final String field) {
         switch (field) {
             case "packagename":
-                pw.println(item.mPackageName);
+                pw.println(item.mOverlay.getPackageName());
+                break;
+            case "overlayname":
+                pw.println(item.mOverlay.getOverlayName());
                 break;
             case "userid":
                 pw.println(item.mUserId);
@@ -392,6 +443,7 @@
         private static final String ATTR_BASE_CODE_PATH = "baseCodePath";
         private static final String ATTR_IS_ENABLED = "isEnabled";
         private static final String ATTR_PACKAGE_NAME = "packageName";
+        private static final String ATTR_OVERLAY_NAME = "overlayName";
         private static final String ATTR_STATE = "state";
         private static final String ATTR_TARGET_PACKAGE_NAME = "targetPackageName";
         private static final String ATTR_TARGET_OVERLAYABLE_NAME = "targetOverlayableName";
@@ -400,30 +452,26 @@
         private static final String ATTR_CATEGORY = "category";
         private static final String ATTR_USER_ID = "userId";
         private static final String ATTR_VERSION = "version";
+        private static final String ATTR_IS_FABRICATED = "fabricated";
 
         @VisibleForTesting
         static final int CURRENT_VERSION = 4;
 
         public static void restore(@NonNull final ArrayList<SettingsItem> table,
                 @NonNull final InputStream is) throws IOException, XmlPullParserException {
+            table.clear();
+            final TypedXmlPullParser parser = Xml.resolvePullParser(is);
+            XmlUtils.beginDocument(parser, TAG_OVERLAYS);
+            final int version = parser.getAttributeInt(null, ATTR_VERSION);
+            if (version != CURRENT_VERSION) {
+                upgrade(version);
+            }
 
-            {
-                table.clear();
-                final TypedXmlPullParser parser = Xml.resolvePullParser(is);
-                XmlUtils.beginDocument(parser, TAG_OVERLAYS);
-                int version = parser.getAttributeInt(null, ATTR_VERSION);
-                if (version != CURRENT_VERSION) {
-                    upgrade(version);
-                }
-                int depth = parser.getDepth();
-
-                while (XmlUtils.nextElementWithin(parser, depth)) {
-                    switch (parser.getName()) {
-                        case TAG_ITEM:
-                            final SettingsItem item = restoreRow(parser, depth + 1);
-                            table.add(item);
-                            break;
-                    }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_ITEM.equals(parser.getName())) {
+                    final SettingsItem item = restoreRow(parser, depth + 1);
+                    table.add(item);
                 }
             }
         }
@@ -447,7 +495,9 @@
 
         private static SettingsItem restoreRow(@NonNull final TypedXmlPullParser parser,
                 final int depth) throws IOException, XmlPullParserException {
-            final String packageName = XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME);
+            final OverlayIdentifier overlay = new OverlayIdentifier(
+                    XmlUtils.readStringAttribute(parser, ATTR_PACKAGE_NAME),
+                    XmlUtils.readStringAttribute(parser, ATTR_OVERLAY_NAME));
             final int userId = parser.getAttributeInt(null, ATTR_USER_ID);
             final String targetPackageName = XmlUtils.readStringAttribute(parser,
                     ATTR_TARGET_PACKAGE_NAME);
@@ -459,9 +509,11 @@
             final boolean isStatic = parser.getAttributeBoolean(null, ATTR_IS_STATIC, false);
             final int priority = parser.getAttributeInt(null, ATTR_PRIORITY);
             final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
+            final boolean isFabricated = parser.getAttributeBoolean(null, ATTR_IS_FABRICATED,
+                    false);
 
-            return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                    baseCodePath, state, isEnabled, !isStatic, priority, category);
+            return new SettingsItem(overlay, userId, targetPackageName, targetOverlayableName,
+                    baseCodePath, state, isEnabled, !isStatic, priority, category, isFabricated);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -484,7 +536,8 @@
         private static void persistRow(@NonNull final TypedXmlSerializer xml,
                 @NonNull final SettingsItem item) throws IOException {
             xml.startTag(null, TAG_ITEM);
-            XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mPackageName);
+            XmlUtils.writeStringAttribute(xml, ATTR_PACKAGE_NAME, item.mOverlay.getPackageName());
+            XmlUtils.writeStringAttribute(xml, ATTR_OVERLAY_NAME, item.mOverlay.getOverlayName());
             xml.attributeInt(null, ATTR_USER_ID, item.mUserId);
             XmlUtils.writeStringAttribute(xml, ATTR_TARGET_PACKAGE_NAME, item.mTargetPackageName);
             XmlUtils.writeStringAttribute(xml, ATTR_TARGET_OVERLAYABLE_NAME,
@@ -495,13 +548,14 @@
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
             xml.attributeInt(null, ATTR_PRIORITY, item.mPriority);
             XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
+            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_FABRICATED, item.mIsFabricated);
             xml.endTag(null, TAG_ITEM);
         }
     }
 
     private static final class SettingsItem {
         private final int mUserId;
-        private final String mPackageName;
+        private final OverlayIdentifier mOverlay;
         private final String mTargetPackageName;
         private final String mTargetOverlayableName;
         private String mBaseCodePath;
@@ -511,13 +565,15 @@
         private boolean mIsMutable;
         private int mPriority;
         private String mCategory;
+        private boolean mIsFabricated;
 
-        SettingsItem(@NonNull final String packageName, final int userId,
+        SettingsItem(@NonNull final OverlayIdentifier overlay, final int userId,
                 @NonNull final String targetPackageName,
                 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
                 final @OverlayInfo.State int state, final boolean isEnabled,
-                final boolean isMutable, final int priority,  @Nullable String category) {
-            mPackageName = packageName;
+                final boolean isMutable, final int priority,  @Nullable String category,
+                final boolean isFabricated) {
+            mOverlay = overlay;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
             mTargetOverlayableName = targetOverlayableName;
@@ -528,6 +584,7 @@
             mCache = null;
             mIsMutable = isMutable;
             mPriority = priority;
+            mIsFabricated = isFabricated;
         }
 
         private String getTargetPackageName() {
@@ -596,8 +653,9 @@
 
         private OverlayInfo getOverlayInfo() {
             if (mCache == null) {
-                mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
-                        mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsMutable);
+                mCache = new OverlayInfo(mOverlay.getPackageName(), mOverlay.getOverlayName(),
+                        mTargetPackageName, mTargetOverlayableName, mCategory, mBaseCodePath,
+                        mState, mUserId, mPriority, mIsMutable, mIsFabricated);
             }
             return mCache;
         }
@@ -620,30 +678,40 @@
         }
     }
 
-    private int select(@NonNull final String packageName, final int userId) {
+    private int select(@NonNull final OverlayIdentifier overlay, final int userId) {
         final int n = mItems.size();
         for (int i = 0; i < n; i++) {
             final SettingsItem item = mItems.get(i);
-            if (item.mUserId == userId && item.mPackageName.equals(packageName)) {
+            if (item.mUserId == userId && item.mOverlay.equals(overlay)) {
                 return i;
             }
         }
         return -1;
     }
 
-    private Stream<SettingsItem> selectWhereUser(final int userId) {
-        return mItems.stream().filter(item -> item.mUserId == userId);
+    private List<SettingsItem> selectWhereUser(final int userId) {
+        final List<SettingsItem> selectedItems = new ArrayList<>();
+        CollectionUtils.addIf(mItems, selectedItems, i -> i.mUserId == userId);
+        return selectedItems;
     }
 
-    private Stream<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+    private List<SettingsItem> selectWhereOverlay(@NonNull final String packageName,
             final int userId) {
-        return selectWhereUser(userId)
-                .filter(item -> item.getTargetPackageName().equals(targetPackageName));
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(i -> !i.mOverlay.getPackageName().equals(packageName));
+        return items;
     }
 
-    static final class BadKeyException extends RuntimeException {
-        BadKeyException(@NonNull final String packageName, final int userId) {
-            super("Bad key mPackageName=" + packageName + " mUserId=" + userId);
+    private List<SettingsItem> selectWhereTarget(@NonNull final String targetPackageName,
+            final int userId) {
+        final List<SettingsItem> items = selectWhereUser(userId);
+        items.removeIf(i -> !i.getTargetPackageName().equals(targetPackageName));
+        return items;
+    }
+
+    static final class BadKeyException extends Exception {
+        BadKeyException(@NonNull final OverlayIdentifier overlay, final int userId) {
+            super("Bad key '" + overlay + "' for user " + userId );
         }
     }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bf99bd6..0b52c2e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -19,20 +19,28 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.om.FabricatedOverlay;
 import android.content.om.IOverlayManager;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.os.Binder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.util.TypedValue;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -72,6 +80,8 @@
                     return runSetPriority();
                 case "lookup":
                     return runLookup();
+                case "fabricate":
+                    return runFabricate();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -89,35 +99,36 @@
         out.println("Overlay manager (overlay) commands:");
         out.println("  help");
         out.println("    Print this help text.");
-        out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
+        out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE[:NAME]]");
         out.println("    Print debugging information about the overlay manager.");
-        out.println("    With optional parameter PACKAGE, limit output to the specified");
-        out.println("    package. With optional parameter FIELD, limit output to");
+        out.println("    With optional parameters PACKAGE and NAME, limit output to the specified");
+        out.println("    overlay or target. With optional parameter FIELD, limit output to");
         out.println("    the value of that SettingsItem field. Field names are");
         out.println("    case insensitive and out.println the m prefix can be omitted,");
         out.println("    so the following are equivalent: mState, mstate, State, state.");
-        out.println("  list [--user USER_ID] [PACKAGE]");
+        out.println("  list [--user USER_ID] [PACKAGE[:NAME]]");
         out.println("    Print information about target and overlay packages.");
         out.println("    Overlay packages are printed in priority order. With optional");
-        out.println("    parameter PACKAGE, limit output to the specified package.");
-        out.println("  enable [--user USER_ID] PACKAGE");
-        out.println("    Enable overlay package PACKAGE.");
-        out.println("  disable [--user USER_ID] PACKAGE");
-        out.println("    Disable overlay package PACKAGE.");
+        out.println("    parameters PACKAGE and NAME, limit output to the specified overlay or");
+        out.println("    target.");
+        out.println("  enable [--user USER_ID] PACKAGE[:NAME]");
+        out.println("    Enable overlay within or owned by PACKAGE with optional unique NAME.");
+        out.println("  disable [--user USER_ID] PACKAGE[:NAME]");
+        out.println("    Disable overlay within or owned by PACKAGE with optional unique NAME.");
         out.println("  enable-exclusive [--user USER_ID] [--category] PACKAGE");
-        out.println("    Enable overlay package PACKAGE and disable all other overlays for");
-        out.println("    its target package. If the --category option is given, only disables");
+        out.println("    Enable overlay within or owned by PACKAGE and disable all other overlays");
+        out.println("    for its target package. If the --category option is given, only disables");
         out.println("    other overlays in the same category.");
         out.println("  set-priority [--user USER_ID] PACKAGE PARENT|lowest|highest");
-        out.println("    Change the priority of the overlay PACKAGE to be just higher than");
-        out.println("    the priority of PACKAGE_PARENT If PARENT is the special keyword");
+        out.println("    Change the priority of the overlay to be just higher than");
+        out.println("    the priority of PARENT If PARENT is the special keyword");
         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
         out.println("    If PARENT is the special keyword 'highest', change priority of");
         out.println("    PACKAGE to the highest priority.");
         out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
         out.println("    Load a package and print the value of a given resource");
         out.println("    applying the current configuration and enabled overlays.");
-        out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
+        out.println("    For a more fine-grained alternative, use 'idmap2 lookup'.");
     }
 
     private int runList() throws RemoteException {
@@ -192,7 +203,7 @@
                 status = "---";
                 break;
         }
-        out.println(String.format("%s %s", status, oi.packageName));
+        out.println(String.format("%s %s", status, oi.getOverlayIdentifier()));
     }
 
     private int runEnableDisable(final boolean enable) throws RemoteException {
@@ -211,8 +222,88 @@
             }
         }
 
-        final String packageName = getNextArgRequired();
-        return mInterface.setEnabled(packageName, enable, userId) ? 0 : 1;
+        final OverlayIdentifier overlay = OverlayIdentifier.fromString(getNextArgRequired());
+        mInterface.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay, enable, userId)
+                .build());
+        return 0;
+    }
+
+    private int runFabricate() throws RemoteException {
+        final PrintWriter err = getErrPrintWriter();
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            err.println("Error: must be root to fabricate overlays through the shell");
+            return 1;
+        }
+
+        int userId = UserHandle.USER_SYSTEM;
+        String targetPackage = "";
+        String targetOverlayable = "";
+        String name = "";
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--target":
+                    targetPackage = getNextArgRequired();
+                    break;
+                case "--target-name":
+                    targetOverlayable = getNextArgRequired();
+                    break;
+                case "--name":
+                    name = getNextArgRequired();
+                    break;
+                default:
+                    err.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (name.isEmpty()) {
+            err.println("Error: Missing required arg '--name'");
+            return 1;
+        }
+
+        if (targetPackage.isEmpty()) {
+            err.println("Error: Missing required arg '--target'");
+            return 1;
+        }
+
+        final String resourceName = getNextArgRequired();
+        final String typeStr = getNextArgRequired();
+        final int type;
+        if (typeStr.startsWith("0x")) {
+            type = Integer.parseUnsignedInt(typeStr.substring(2), 16);
+        } else {
+            type = Integer.parseUnsignedInt(typeStr);
+        }
+        final String dataStr = getNextArgRequired();
+        final int data;
+        if (dataStr.startsWith("0x")) {
+            data = Integer.parseUnsignedInt(dataStr.substring(2), 16);
+        } else {
+            data = Integer.parseUnsignedInt(dataStr);
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final String overlayPackageName = "com.android.shell";
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                overlayPackageName, name, targetPackage)
+                .setTargetOverlayable(targetOverlayable)
+                .setResourceValue(resourceName, type, data)
+                .build();
+
+        mInterface.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+        return 0;
     }
 
     private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/om/PackageManagerHelper.java b/services/core/java/com/android/server/om/PackageManagerHelper.java
index b1a8b4e..750f5c3 100644
--- a/services/core/java/com/android/server/om/PackageManagerHelper.java
+++ b/services/core/java/com/android/server/om/PackageManagerHelper.java
@@ -22,10 +22,15 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
 
 import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -36,6 +41,33 @@
  * @hide
  */
 interface PackageManagerHelper {
+
+    /**
+     * Initializes the helper for the user. This only needs to be invoked one time before
+     * packages of this user are queried.
+     * @param userId the user id to initialize
+     * @return a map of package name to all packages installed in the user
+     */
+    @NonNull
+    ArrayMap<String, AndroidPackage> initializeForUser(final int userId);
+
+    /**
+     * Retrieves the package information if it is installed for the user.
+     */
+    @Nullable
+    AndroidPackage getPackageForUser(@NonNull final String packageName, final int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    boolean isInstantApp(@NonNull final String packageName, final int userId);
+
+    /**
+     * @see PackageManager#getPackagesForUid(int)
+     */
+    @Nullable
+    String[] getPackagesForUid(int uid);
+
     /**
      * @return true if the target package has declared an overlayable
      */
@@ -64,11 +96,6 @@
     Map<String, Map<String, String>> getNamedActors();
 
     /**
-     * @see PackageManagerInternal#getOverlayPackages(int)
-     */
-    List<PackageInfo> getOverlayPackages(int userId);
-
-    /**
      * Read from the APK and AndroidManifest of a package to return the overlayable defined for
      * a given name.
      *
@@ -80,19 +107,6 @@
             throws IOException;
 
     /**
-     * @see PackageManager#getPackagesForUid(int)
-     */
-    @Nullable
-    String[] getPackagesForUid(int uid);
-
-    /**
-     * @param userId user to filter package visibility by
-     * @see PackageManager#getPackageInfo(String, int)
-     */
-    @Nullable
-    PackageInfo getPackageInfo(@NonNull String packageName, int userId);
-
-    /**
      * @return true if {@link PackageManagerServiceUtils#compareSignatures} run on both packages
      *     in the system returns {@link PackageManager#SIGNATURE_MATCH}
      */
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index de85d9e..f31d1da 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -43,6 +43,7 @@
 import android.util.ArraySet;
 import android.util.Singleton;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,12 @@
     abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
 
     /**
+     * Returns array of all staged sessions known to apexd.
+     */
+    @NonNull
+    abstract SparseArray<ApexSessionInfo> getSessions();
+
+    /**
      * Submit a staged session to apex service. This causes the apex service to perform some initial
      * verification and accept or reject the session. Submitting a session successfully is not
      * enough for it to be activated at the next boot, the caller needs to call
@@ -691,6 +698,21 @@
         }
 
         @Override
+        SparseArray<ApexSessionInfo> getSessions() {
+            try {
+                final ApexSessionInfo[] sessions = waitForApexService().getSessions();
+                final SparseArray<ApexSessionInfo> result = new SparseArray<>(sessions.length);
+                for (int i = 0; i < sessions.length; i++) {
+                    result.put(sessions[i].sessionId, sessions[i]);
+                }
+                return result;
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
         ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
             try {
                 final ApexInfoList apexInfoList = new ApexInfoList();
@@ -1083,6 +1105,11 @@
         }
 
         @Override
+        SparseArray<ApexSessionInfo> getSessions() {
+            return new SparseArray<>(0);
+        }
+
+        @Override
         ApexInfoList submitStagedSession(ApexSessionParams params)
                 throws PackageManagerException {
             throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 402f646..af0aa76 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -217,7 +217,7 @@
             // trade-off worth doing to save boot time work.
             int result = pm.performDexOptWithStatus(new DexoptOptions(
                     pkg,
-                    PackageManagerService.REASON_BOOT,
+                    PackageManagerService.REASON_POST_BOOT,
                     DexoptOptions.DEXOPT_BOOT_COMPLETE));
             if (result == PackageDexOptimizer.DEX_OPT_PERFORMED)  {
                 updatedPackages.add(pkg);
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 52fdc79..308e815 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -27,6 +27,8 @@
 import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -45,12 +47,20 @@
 public class DataLoaderManagerService extends SystemService {
     private static final String TAG = "DataLoaderManager";
     private final Context mContext;
+    private final HandlerThread mThread;
+    private final Handler mHandler;
     private final DataLoaderManagerBinderService mBinderService;
     private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>();
 
     public DataLoaderManagerService(Context context) {
         super(context);
         mContext = context;
+
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+
+        mHandler = new Handler(mThread.getLooper());
+
         mBinderService = new DataLoaderManagerBinderService();
     }
 
@@ -62,7 +72,7 @@
     final class DataLoaderManagerBinderService extends IDataLoaderManager.Stub {
         @Override
         public boolean bindToDataLoader(int dataLoaderId, DataLoaderParamsParcel params,
-                IDataLoaderStatusListener listener) {
+                long bindDelayMs, IDataLoaderStatusListener listener) {
             synchronized (mServiceConnections) {
                 if (mServiceConnections.get(dataLoaderId) != null) {
                     return true;
@@ -76,19 +86,21 @@
             }
 
             // Binds to the specific data loader service.
-            DataLoaderServiceConnection connection = new DataLoaderServiceConnection(dataLoaderId,
-                    listener);
+            final DataLoaderServiceConnection connection = new DataLoaderServiceConnection(
+                    dataLoaderId, listener);
 
-            Intent intent = new Intent();
+            final Intent intent = new Intent();
             intent.setComponent(dataLoaderComponent);
-            if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
-                    UserHandle.of(UserHandle.getCallingUserId()))) {
-                Slog.e(TAG,
-                        "Failed to bind to: " + dataLoaderComponent + " for ID=" + dataLoaderId);
-                mContext.unbindService(connection);
-                return false;
-            }
-            return true;
+
+            return mHandler.postDelayed(() -> {
+                if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+                        mHandler, UserHandle.of(UserHandle.getCallingUserId()))) {
+                    Slog.e(TAG,
+                            "Failed to bind to: " + dataLoaderComponent + " for ID="
+                                    + dataLoaderId);
+                    mContext.unbindService(connection);
+                }
+            }, bindDelayMs);
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f240d85..b06e84d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -929,11 +930,23 @@
                 // Flag for bubble to make behaviour match documentLaunchMode=always.
                 intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                 intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                intents[0].putExtra(Intent.EXTRA_IS_BUBBLED, true);
             }
 
             intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             intents[0].setSourceBounds(sourceBounds);
 
+            // Replace theme for splash screen
+            final int splashScreenThemeResId =
+                    mShortcutServiceInternal.getShortcutStartingThemeResId(getCallingUserId(),
+                            callingPackage, packageName, shortcutId, targetUserId);
+            if (splashScreenThemeResId != 0) {
+                if (startActivityOptions == null) {
+                    startActivityOptions = new Bundle();
+                }
+                startActivityOptions.putInt(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResId);
+            }
+
             return startShortcutIntentsAsPublisher(
                     intents, packageName, featureId, startActivityOptions, targetUserId);
         }
@@ -1317,6 +1330,10 @@
                     mListeners.finishBroadcast();
                 }
                 super.onPackageAdded(packageName, uid);
+                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+                pmi.registerInstalledLoadingProgressCallback(packageName,
+                        new PackageLoadingProgressCallback(packageName, user),
+                        user.getIdentifier());
             }
 
             @Override
@@ -1538,5 +1555,38 @@
                 checkCallbackCount();
             }
         }
+
+        class PackageLoadingProgressCallback extends
+                PackageManagerInternal.InstalledLoadingProgressCallback {
+            private String mPackageName;
+            private UserHandle mUser;
+
+            PackageLoadingProgressCallback(String packageName, UserHandle user) {
+                super(mCallbackHandler);
+                mPackageName = packageName;
+                mUser = user;
+            }
+
+            @Override
+            public void onLoadingProgressChanged(float progress) {
+                final int n = mListeners.beginBroadcast();
+                try {
+                    for (int i = 0; i < n; i++) {
+                        IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
+                        BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
+                        if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
+                            continue;
+                        }
+                        try {
+                            listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress);
+                        } catch (RemoteException re) {
+                            Slog.d(TAG, "Callback failed ", re);
+                        }
+                    }
+                } finally {
+                    mListeners.finishBroadcast();
+                }
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fc02b34..b9e3e0f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -687,7 +687,8 @@
         boolean generateCompactDex = true;
         switch (compilationReason) {
             case PackageManagerService.REASON_FIRST_BOOT:
-            case PackageManagerService.REASON_BOOT:
+            case PackageManagerService.REASON_BOOT_AFTER_OTA:
+            case PackageManagerService.REASON_POST_BOOT:
             case PackageManagerService.REASON_INSTALL:
                  generateCompactDex = false;
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2d393c0..2812830 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -295,23 +295,29 @@
         synchronized (mSessions) {
             for (int i = 0; i < mSessions.size(); i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
-                if (session.isStaged()) {
-                    stagedSessionsToRestore.add(session.mStagedSession);
+                if (!session.isStaged()) {
+                    continue;
+                }
+                StagingManager.StagedSession stagedSession = session.mStagedSession;
+                if (!stagedSession.isInTerminalState() && stagedSession.hasParentSessionId()
+                        && getSession(stagedSession.getParentSessionId()) == null) {
+                    stagedSession.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                            "An orphan staged session " + stagedSession.sessionId() + " is found, "
+                                + "parent " + stagedSession.getParentSessionId() + " is missing");
+                    continue;
+                }
+                if (!stagedSession.hasParentSessionId() && stagedSession.isCommitted()
+                        && !stagedSession.isInTerminalState()) {
+                    // StagingManager.restoreSessions expects a list of committed, non-finalized
+                    // parent staged sessions.
+                    stagedSessionsToRestore.add(stagedSession);
                 }
             }
         }
-        // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
+        // Don't hold mSessions lock when calling restoreSessions, since it might trigger an APK
         // atomic install which needs to query sessions, which requires lock on mSessions.
-        boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
-        for (StagingManager.StagedSession session : stagedSessionsToRestore) {
-            if (!session.isInTerminalState() && session.hasParentSessionId()
-                    && getSession(session.getParentSessionId()) == null) {
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "An orphan staged session " + session.sessionId() + " is found, "
-                                + "parent " + session.getParentSessionId() + " is missing");
-            }
-            mStagingManager.restoreSession(session, isDeviceUpgrading);
-        }
+        // Note: restoreSessions mutates content of stagedSessionsToRestore.
+        mStagingManager.restoreSessions(stagedSessionsToRestore, mPm.isDeviceUpgrading());
     }
 
     @GuardedBy("mSessions")
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c42569..0ce2673 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3778,7 +3778,9 @@
             }
         }
 
-        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
+        final long bindDelayMs = 0;
+        if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), bindDelayMs,
+                statusListener)) {
             throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                     "Failed to initialize data loader");
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b725c96..fc18ddb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -193,7 +193,6 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
 import android.content.pm.ModuleInfo;
-import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.PackageChangeEvent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
@@ -462,6 +461,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -780,17 +780,18 @@
     // Compilation reasons.
     public static final int REASON_UNKNOWN = -1;
     public static final int REASON_FIRST_BOOT = 0;
-    public static final int REASON_BOOT = 1;
-    public static final int REASON_INSTALL = 2;
-    public static final int REASON_INSTALL_FAST = 3;
-    public static final int REASON_INSTALL_BULK = 4;
-    public static final int REASON_INSTALL_BULK_SECONDARY = 5;
-    public static final int REASON_INSTALL_BULK_DOWNGRADED = 6;
-    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7;
-    public static final int REASON_BACKGROUND_DEXOPT = 8;
-    public static final int REASON_AB_OTA = 9;
-    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10;
-    public static final int REASON_SHARED = 11;
+    public static final int REASON_BOOT_AFTER_OTA = 1;
+    public static final int REASON_POST_BOOT = 2;
+    public static final int REASON_INSTALL = 3;
+    public static final int REASON_INSTALL_FAST = 4;
+    public static final int REASON_INSTALL_BULK = 5;
+    public static final int REASON_INSTALL_BULK_SECONDARY = 6;
+    public static final int REASON_INSTALL_BULK_DOWNGRADED = 7;
+    public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8;
+    public static final int REASON_BACKGROUND_DEXOPT = 9;
+    public static final int REASON_AB_OTA = 10;
+    public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
+    public static final int REASON_SHARED = 12;
 
     public static final int REASON_LAST = REASON_SHARED;
 
@@ -1749,6 +1750,11 @@
         public AndroidPackage getPackage(@NonNull String packageName) {
             return getPackageLocked(packageName);
         }
+
+        @Override
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            return mPmInternal.filterAppAccess(packageName, callingUid, userId);
+        }
     }
 
     /**
@@ -2580,49 +2586,42 @@
                 Intent intent, int matchFlags, List<ResolveInfo> candidates,
                 CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
             final ArrayList<ResolveInfo> result = new ArrayList<>();
-            final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
-            final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
             final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
-            final int count = candidates.size();
-            // First, try to use linked apps. Partition the candidates into four lists:
-            // one for the final results, one for the "do not use ever", one for "undefined status"
-            // and finally one for "browser app type".
-            for (int n=0; n<count; n++) {
-                ResolveInfo info = candidates.get(n);
-                String packageName = info.activityInfo.packageName;
-                PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    // Add to the special match all list (Browser use case)
-                    if (info.handleAllWebDataURI) {
-                        matchAllList.add(info);
-                        continue;
-                    }
 
-                    boolean isAlways = mDomainVerificationManager
-                            .isApprovedForDomain(ps, intent, userId);
-                    if (isAlways) {
-                        alwaysList.add(info);
-                    } else {
-                        undefinedList.add(info);
-                    }
-                    continue;
+            final int count = candidates.size();
+            // First, try to use approved apps.
+            for (int n = 0; n < count; n++) {
+                ResolveInfo info = candidates.get(n);
+                // Add to the special match all list (Browser use case)
+                if (info.handleAllWebDataURI) {
+                    matchAllList.add(info);
                 }
             }
 
+            Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
+                    .filterToApprovedApp(intent, candidates, userId, mSettings::getPackageLPr);
+            List<ResolveInfo> approvedInfos = infosAndLevel.first;
+            Integer highestApproval = infosAndLevel.second;
+
             // We'll want to include browser possibilities in a few cases
             boolean includeBrowser = false;
 
-            // First try to add the "always" resolution(s) for the current user, if any
-            if (alwaysList.size() > 0) {
-                result.addAll(alwaysList);
+            // If no apps are approved for the domain, resolve only to browsers
+            if (approvedInfos.isEmpty()) {
+                // If the other profile has a result, include that and delegate to ResolveActivity
+                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
+                        > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
+                    result.add(xpDomainInfo.resolveInfo);
+                } else {
+                    includeBrowser = true;
+                }
             } else {
-                // Add all undefined apps as we want them to appear in the disambiguation dialog.
-                result.addAll(undefinedList);
-                // Maybe add one for the other profile.
-                if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
+                result.addAll(approvedInfos);
+
+                // If the other profile has an app that's of equal or higher approval, add it
+                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel >= highestApproval) {
                     result.add(xpDomainInfo.resolveInfo);
                 }
-                includeBrowser = true;
             }
 
             if (includeBrowser) {
@@ -2670,9 +2669,7 @@
                     }
                 }
 
-                // If there is nothing selected, add all candidates and remove the ones that the
-                //user
-                // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
+                // If there is nothing selected, add all candidates
                 if (result.size() == 0) {
                     result.addAll(candidates);
                 }
@@ -2774,10 +2771,12 @@
                             sourceUserId, parentUserId);
                 }
 
-                result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
-                        .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
+                result.highestApprovalLevel = Math.max(mDomainVerificationManager
+                        .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId),
+                        result.highestApprovalLevel);
             }
-            if (result != null && !result.wereAnyDomainsVerificationApproved) {
+            if (result != null && result.highestApprovalLevel
+                    <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
                 return null;
             }
             return result;
@@ -3020,9 +3019,10 @@
                     final String packageName = info.activityInfo.packageName;
                     final PackageSetting ps = mSettings.getPackageLPr(packageName);
                     if (ps.getInstantApp(userId)) {
-                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+                                userId)) {
                             if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "Instant app approvd for intent; pkg: "
+                                Slog.v(TAG, "Instant app approved for intent; pkg: "
                                         + packageName);
                             }
                             localInstantApp = info;
@@ -3947,7 +3947,8 @@
                 if (ps != null) {
                     // only check domain verification status if the app is not a browser
                     if (!info.handleAllWebDataURI) {
-                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
+                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
+                                userId)) {
                             if (DEBUG_INSTANT) {
                                 Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
                                         + ", approved");
@@ -9363,8 +9364,8 @@
                     if (ri.activityInfo.applicationInfo.isInstantApp()) {
                         final String packageName = ri.activityInfo.packageName;
                         final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                        if (ps != null && mDomainVerificationManager
-                                .isApprovedForDomain(ps, intent, userId)) {
+                        if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps,
+                                intent, userId)) {
                             return ri;
                         }
                     }
@@ -9414,6 +9415,19 @@
     }
 
     /**
+     * Do NOT use for intent resolution filtering. That should be done with
+     * {@link DomainVerificationManagerInternal#filterToApprovedApp(Intent, List, int, Function)}.
+     *
+     * @return if the package is approved at any non-zero level for the domain in the intent
+     */
+    private static boolean hasAnyDomainApproval(
+            @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting,
+            @NonNull Intent intent, @UserIdInt int userId) {
+        return manager.approvalLevelForDomain(pkgSetting, intent, userId)
+                > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
+    }
+
+    /**
      * Return true if the given list is not empty and all of its contents have
      * an activityInfo with the given package name.
      */
@@ -9856,7 +9870,7 @@
     private static class CrossProfileDomainInfo {
         /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
         ResolveInfo resolveInfo;
-        boolean wereAnyDomainsVerificationApproved;
+        int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
     }
 
     private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -11632,10 +11646,7 @@
         //       first boot, as they do not have profile data.
         boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;
 
-        // We need to re-extract after a pruned cache, as AoT-ed files will be out of date.
-        boolean causePrunedCache = VMRuntime.didPruneDalvikCache();
-
-        if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {
+        if (!causeUpgrade && !causeFirstBoot) {
             return;
         }
 
@@ -11652,7 +11663,7 @@
 
         final long startTime = System.nanoTime();
         final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,
-                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,
+                    causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
                     false /* bootComplete */);
 
         final int elapsedTimeSeconds =
@@ -16157,8 +16168,7 @@
     @Deprecated
     @Override
     public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
-        mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
-        return true;
+        return mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
     }
 
     @Deprecated
@@ -25945,6 +25955,13 @@
         public String getModuleMetadataPackageName() throws RemoteException {
             return PackageManagerService.this.mModuleInfoProvider.getPackageName();
         }
+
+        @Override
+        public boolean hasSha256SigningCertificate(String packageName, byte[] certificate)
+                throws RemoteException {
+            return PackageManagerService.this.hasSigningCertificate(
+                packageName, certificate, CERT_INPUT_SHA256);
+        }
     }
 
     private AndroidPackage getPackage(String packageName) {
@@ -26202,30 +26219,7 @@
 
         @Override
         public void setKeepUninstalledPackages(final List<String> packageList) {
-            Preconditions.checkNotNull(packageList);
-            List<String> removedFromList = null;
-            synchronized (mLock) {
-                if (mKeepUninstalledPackages != null) {
-                    final int packagesCount = mKeepUninstalledPackages.size();
-                    for (int i = 0; i < packagesCount; i++) {
-                        String oldPackage = mKeepUninstalledPackages.get(i);
-                        if (packageList != null && packageList.contains(oldPackage)) {
-                            continue;
-                        }
-                        if (removedFromList == null) {
-                            removedFromList = new ArrayList<>();
-                        }
-                        removedFromList.add(oldPackage);
-                    }
-                }
-                mKeepUninstalledPackages = new ArrayList<>(packageList);
-                if (removedFromList != null) {
-                    final int removedCount = removedFromList.size();
-                    for (int i = 0; i < removedCount; i++) {
-                        deletePackageIfUnusedLPr(removedFromList.get(i));
-                    }
-                }
-            }
+            PackageManagerService.this.setKeepUninstalledPackagesInternal(packageList);
         }
 
         @Override
@@ -27025,6 +27019,28 @@
         }
 
         @Override
+        public boolean registerInstalledLoadingProgressCallback(String packageName,
+                PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
+            final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
+                    userId);
+            if (ps == null) {
+                return false;
+            }
+            if (!ps.isPackageLoading()) {
+                Slog.w(TAG,
+                        "Failed registering loading progress callback. Package is fully loaded.");
+                return false;
+            }
+            if (mIncrementalManager == null) {
+                Slog.w(TAG,
+                        "Failed registering loading progress callback. Incremental is not enabled");
+                return false;
+            }
+            return mIncrementalManager.registerLoadingProgressCallback(ps.getPathString(),
+                    (IPackageLoadingProgressCallback) callback.getBinder());
+        }
+
+        @Override
         public IncrementalStatesInfo getIncrementalStatesInfo(
                 @NonNull String packageName, int filterCallingUid, int userId) {
             final PackageSetting ps = getPackageSettingForUser(packageName, filterCallingUid,
@@ -27701,6 +27717,43 @@
     public DomainVerificationService.Connection getDomainVerificationConnection() {
         return mDomainVerificationConnection;
     }
+
+    @Override
+    public void setKeepUninstalledPackages(List<String> packageList) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.KEEP_UNINSTALLED_PACKAGES,
+                "setKeepUninstalledPackages requires KEEP_UNINSTALLED_PACKAGES permission");
+        Objects.requireNonNull(packageList);
+
+        setKeepUninstalledPackagesInternal(packageList);
+    }
+
+    private void setKeepUninstalledPackagesInternal(List<String> packageList) {
+        Preconditions.checkNotNull(packageList);
+        List<String> removedFromList = null;
+        synchronized (mLock) {
+            if (mKeepUninstalledPackages != null) {
+                final int packagesCount = mKeepUninstalledPackages.size();
+                for (int i = 0; i < packagesCount; i++) {
+                    String oldPackage = mKeepUninstalledPackages.get(i);
+                    if (packageList != null && packageList.contains(oldPackage)) {
+                        continue;
+                    }
+                    if (removedFromList == null) {
+                        removedFromList = new ArrayList<>();
+                    }
+                    removedFromList.add(oldPackage);
+                }
+            }
+            mKeepUninstalledPackages = new ArrayList<>(packageList);
+            if (removedFromList != null) {
+                final int removedCount = removedFromList.size();
+                for (int i = 0; i < removedCount; i++) {
+                    deletePackageIfUnusedLPr(removedFromList.get(i));
+                }
+            }
+        }
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 9cd55a6..636db11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -29,7 +29,8 @@
     // Names for compilation reasons.
     public static final String REASON_STRINGS[] = {
         "first-boot",
-        "boot",
+        "boot-after-ota",
+        "post-boot",
         "install",
         "install-fast",
         "install-bulk",
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 5364cbf..a83a3f8 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -783,6 +783,10 @@
         incrementalStates.onStorageHealthStatusChanged(status);
     }
 
+    public long getFirstInstallTime() {
+        return firstInstallTime;
+    }
+
     protected PackageSettingBase updateFrom(PackageSettingBase other) {
         super.copyFrom(other);
         setPath(other.getPath());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e73486a..2112247 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -72,7 +72,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
@@ -2615,6 +2614,8 @@
         } else {
             serializer.attributeInt(null, "sharedUserId", pkg.appId);
         }
+        serializer.attributeFloat(null, "loadingProgress",
+                pkg.getIncrementalStates().getProgress());
 
         writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
 
@@ -2712,7 +2713,6 @@
         writeSigningKeySetLPr(serializer, pkg.keySetData);
         writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
         writeKeySetAliasesLPr(serializer, pkg.keySetData);
-        mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
         writeMimeGroupLPr(serializer, pkg.mimeGroups);
 
         serializer.endTag(null, "package");
@@ -3390,6 +3390,9 @@
         if (ps.appId <= 0) {
             ps.appId = parser.getAttributeInt(null, "sharedUserId", 0);
         }
+        final float loadingProgress =
+                parser.getAttributeFloat(null, "loadingProgress", 0);
+        ps.setLoadingProgress(loadingProgress);
 
         int outerDepth = parser.getDepth();
         int type;
@@ -4583,7 +4586,7 @@
             pw.print(prefix); pw.print("  installerAttributionTag=");
             pw.println(ps.installSource.installerAttributionTag);
         }
-        if (IncrementalManager.isIncrementalPath(ps.getPathString())) {
+        if (ps.isPackageLoading()) {
             pw.print(prefix); pw.println("  loadingProgress="
                     + (int) (ps.getIncrementalStates().getProgress() * 100) + "%");
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 9b092c0..bb4ec16 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -112,6 +112,7 @@
     private static final String ATTR_BITMAP_PATH = "bitmap-path";
     private static final String ATTR_ICON_URI = "icon-uri";
     private static final String ATTR_LOCUS_ID = "locus-id";
+    private static final String ATTR_SPLASH_SCREEN_THEME_ID = "splash-screen-theme-id";
 
     private static final String ATTR_PERSON_NAME = "name";
     private static final String ATTR_PERSON_URI = "uri";
@@ -1654,6 +1655,7 @@
         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
         ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
+        ShortcutService.writeAttr(out, ATTR_SPLASH_SCREEN_THEME_ID, si.getStartingThemeResId());
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
@@ -1861,6 +1863,7 @@
         String bitmapPath;
         String iconUri;
         final String locusIdString;
+        int splashScreenThemeResId;
         int backupVersionCode;
         ArraySet<String> categories = null;
         ArrayList<Person> persons = new ArrayList<>();
@@ -1871,6 +1874,8 @@
         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
         titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
         titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
+        splashScreenThemeResId = ShortcutService.parseIntAttribute(parser,
+                ATTR_SPLASH_SCREEN_THEME_ID);
         text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
         textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
         textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
@@ -1964,7 +1969,8 @@
                 intents.toArray(new Intent[intents.size()]),
                 rank, extras, lastChangedTimestamp, flags,
                 iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons.toArray(new Person[persons.size()]), locusId);
+                disabledReason, persons.toArray(new Person[persons.size()]), locusId,
+                splashScreenThemeResId);
     }
 
     private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index d3aace1..c06f01a 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -383,6 +383,8 @@
             final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
             final int disabledMessageResId = sa.getResourceId(
                     R.styleable.Shortcut_shortcutDisabledMessage, 0);
+            final int splashScreenTheme = sa.getResourceId(
+                    R.styleable.Shortcut_splashScreenTheme, 0);
 
             if (TextUtils.isEmpty(id)) {
                 Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
@@ -404,7 +406,8 @@
                     disabledMessageResId,
                     rank,
                     iconResId,
-                    enabled);
+                    enabled,
+                    splashScreenTheme);
         } finally {
             sa.recycle();
         }
@@ -413,7 +416,7 @@
     private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
             @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
             int titleResId, int textResId, int disabledMessageResId,
-            int rank, int iconResId, boolean enabled) {
+            int rank, int iconResId, boolean enabled, int splashScreenTheme) {
 
         final int flags =
                 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
@@ -452,7 +455,8 @@
                 null, // icon Url
                 disabledReason,
                 null /* persons */,
-                null /* locusId */);
+                null /* locusId */,
+                splashScreenTheme);
     }
 
     private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 863e3fe5..4d8abea 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3214,6 +3214,32 @@
         }
 
         @Override
+        public int getShortcutStartingThemeResId(int launcherUserId,
+                @NonNull String callingPackage, @NonNull String packageName,
+                @NonNull String shortcutId, int userId) {
+            Objects.requireNonNull(callingPackage, "callingPackage");
+            Objects.requireNonNull(packageName, "packageName");
+            Objects.requireNonNull(shortcutId, "shortcutId");
+
+            synchronized (mLock) {
+                throwIfUserLockedL(userId);
+                throwIfUserLockedL(launcherUserId);
+
+                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
+                        .attemptToRestoreIfNeededAndSave();
+
+                final ShortcutPackage p = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (p == null) {
+                    return 0;
+                }
+
+                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
+                return shortcutInfo != null ? shortcutInfo.getStartingThemeResId() : 0;
+            }
+        }
+
+        @Override
         public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
                 @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull String shortcutId, int userId) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 545567c..0a74032 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -50,8 +50,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.IntArray;
@@ -63,6 +61,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
@@ -143,10 +142,16 @@
     }
 
     StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
+        this(context, packageParserSupplier, ApexManager.getInstance());
+    }
+
+    @VisibleForTesting
+    StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier,
+            ApexManager apexManager) {
         mContext = context;
         mPackageParserSupplier = packageParserSupplier;
 
-        mApexManager = ApexManager.getInstance();
+        mApexManager = apexManager;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
                 BackgroundThread.get().getLooper());
@@ -354,11 +359,11 @@
     }
 
     // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
-    private void abortCheckpoint(int sessionId, String errorMsg) {
-        String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
+    private void abortCheckpoint(String failureReason, boolean supportsCheckpoint,
+            boolean needsCheckpoint) {
         Slog.e(TAG, failureReason);
         try {
-            if (supportsCheckpoint() && needsCheckpoint()) {
+            if (supportsCheckpoint && needsCheckpoint) {
                 // Store failure reason for next reboot
                 try (BufferedWriter writer =
                              new BufferedWriter(new FileWriter(mFailureReasonFile))) {
@@ -371,8 +376,9 @@
                 if (mApexManager.isApexSupported()) {
                     mApexManager.revertActiveSessions();
                 }
+
                 PackageHelper.getStorageManager().abortChanges(
-                        "StagingManager initiated", false /*retry*/);
+                        "abort-staged-install", false /*retry*/);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, "Failed to abort checkpoint", e);
@@ -384,14 +390,6 @@
         }
     }
 
-    private boolean supportsCheckpoint() throws RemoteException {
-        return PackageHelper.getStorageManager().supportsCheckpoint();
-    }
-
-    private boolean needsCheckpoint() throws RemoteException {
-        return PackageHelper.getStorageManager().needsCheckpoint();
-    }
-
     /**
      * Utility function for extracting apex sessions out of multi-package/single session.
      */
@@ -517,96 +515,31 @@
         }
     }
 
-    private void resumeSession(@NonNull StagedSession session)
-            throws PackageManagerException {
+    private void resumeSession(@NonNull StagedSession session, boolean supportsCheckpoint,
+            boolean needsCheckpoint) throws PackageManagerException {
         Slog.d(TAG, "Resuming session " + session.sessionId());
 
         final boolean hasApex = session.containsApexSession();
-        ApexSessionInfo apexSessionInfo = null;
-        if (hasApex) {
-            // Check with apexservice whether the apex packages have been activated.
-            apexSessionInfo = mApexManager.getStagedSessionInfo(session.sessionId());
-
-            // Prepare for logging a native crash during boot, if one occurred.
-            if (apexSessionInfo != null && !TextUtils.isEmpty(
-                    apexSessionInfo.crashingNativeProcess)) {
-                prepareForLoggingApexdRevert(session, apexSessionInfo.crashingNativeProcess);
-            }
-
-            if (apexSessionInfo != null && apexSessionInfo.isVerified) {
-                // Session has been previously submitted to apexd, but didn't complete all the
-                // pre-reboot verification, perhaps because the device rebooted in the meantime.
-                // Greedily re-trigger the pre-reboot verification. We want to avoid marking it as
-                // failed when not in checkpoint mode, hence it is being processed separately.
-                Slog.d(TAG, "Found pending staged session " + session.sessionId() + " still to "
-                        + "be verified, resuming pre-reboot verification");
-                mPreRebootVerificationHandler.startPreRebootVerification(session);
-                return;
-            }
-        }
 
         // Before we resume session, we check if revert is needed or not. Typically, we enter file-
         // system checkpoint mode when we reboot first time in order to install staged sessions. We
         // want to install staged sessions in this mode as rebooting now will revert user data. If
         // something goes wrong, then we reboot again to enter fs-rollback mode. Rebooting now will
         // have no effect on user data, so mark the sessions as failed instead.
-        try {
-            // If checkpoint is supported, then we only resume sessions if we are in checkpointing
-            // mode. If not, we fail all sessions.
-            if (supportsCheckpoint() && !needsCheckpoint()) {
-                String revertMsg = "Reverting back to safe state. Marking "
-                        + session.sessionId() + " as failed.";
-                final String reasonForRevert = getReasonForRevert();
-                if (!TextUtils.isEmpty(reasonForRevert)) {
-                    revertMsg += " Reason for revert: " + reasonForRevert;
-                }
-                Slog.d(TAG, revertMsg);
-                session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
-                return;
+        // If checkpoint is supported, then we only resume sessions if we are in checkpointing mode.
+        // If not, we fail all sessions.
+        if (supportsCheckpoint && !needsCheckpoint) {
+            String revertMsg = "Reverting back to safe state. Marking " + session.sessionId()
+                    + " as failed.";
+            final String reasonForRevert = getReasonForRevert();
+            if (!TextUtils.isEmpty(reasonForRevert)) {
+                revertMsg += " Reason for revert: " + reasonForRevert;
             }
-        } catch (RemoteException e) {
-            // Cannot continue staged install without knowing if fs-checkpoint is supported
-            Slog.e(TAG, "Checkpoint support unknown. Aborting staged install for session "
-                    + session.sessionId(), e);
-            // TODO: Mark all staged sessions together and reboot only once
-            session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
-                    "Checkpoint support unknown. Aborting staged install.");
-            if (hasApex) {
-                mApexManager.revertActiveSessions();
-            }
-            mPowerManager.reboot("Checkpoint support unknown");
+            Slog.d(TAG, revertMsg);
+            session.setSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, revertMsg);
             return;
         }
 
-        // Check if apex packages in the session failed to activate
-        if (hasApex) {
-            if (apexSessionInfo == null) {
-                final String errorMsg = "apexd did not know anything about a staged session "
-                        + "supposed to be activated";
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-            if (isApexSessionFailed(apexSessionInfo)) {
-                String errorMsg = "APEX activation failed. Check logcat messages from apexd "
-                        + "for more information.";
-                if (!TextUtils.isEmpty(mNativeFailureReason)) {
-                    errorMsg = "Session reverted due to crashing native process: "
-                            + mNativeFailureReason;
-                }
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-            if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
-                // Apexd did not apply the session for some unknown reason. There is no
-                // guarantee that apexd will install it next time. Safer to proactively mark
-                // it as failed.
-                final String errorMsg = "Staged session " + session.sessionId() + "at boot "
-                        + "didn't activate nor fail. Marking it as failed anyway.";
-                throw new PackageManagerException(
-                        SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
-            }
-        }
-
         // Handle apk and apk-in-apex installation
         if (hasApex) {
             checkInstallationOfApkInApexSuccessful(session);
@@ -622,28 +555,24 @@
         Slog.d(TAG, "Marking session " + session.sessionId() + " as applied");
         session.setSessionApplied();
         if (hasApex) {
-            try {
-                if (supportsCheckpoint()) {
-                    // Store the session ID, which will be marked as successful by ApexManager
-                    // upon boot completion.
-                    synchronized (mSuccessfulStagedSessionIds) {
-                        mSuccessfulStagedSessionIds.add(session.sessionId());
-                    }
-                } else {
-                    // Mark sessions as successful immediately on non-checkpointing devices.
-                    mApexManager.markStagedSessionSuccessful(session.sessionId());
+            if (supportsCheckpoint) {
+                // Store the session ID, which will be marked as successful by ApexManager upon
+                // boot completion.
+                synchronized (mSuccessfulStagedSessionIds) {
+                    mSuccessfulStagedSessionIds.add(session.sessionId());
                 }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Checkpoint support unknown, marking session as successful "
-                        + "immediately.");
+            } else {
+                // Mark sessions as successful immediately on non-checkpointing devices.
                 mApexManager.markStagedSessionSuccessful(session.sessionId());
             }
         }
     }
 
-    void onInstallationFailure(StagedSession session, PackageManagerException e) {
+    void onInstallationFailure(StagedSession session, PackageManagerException e,
+            boolean supportsCheckpoint, boolean needsCheckpoint) {
         session.setSessionFailed(e.error, e.getMessage());
-        abortCheckpoint(session.sessionId(), e.getMessage());
+        abortCheckpoint("Failed to install sessionId: " + session.sessionId()
+                + " Error: " + e.getMessage(), supportsCheckpoint, needsCheckpoint);
 
         // If checkpoint is not supported, we have to handle failure for one staged session.
         if (!session.containsApexSession()) {
@@ -767,8 +696,13 @@
                     "Cannot stage session " + session.sessionId() + " with package name null");
         }
 
-        boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
-                Context.STORAGE_SERVICE)).isCheckpointSupported();
+        boolean supportsCheckpoint;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+        } catch (RemoteException e) {
+            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                    "Can't query fs-checkpoint status : " + e);
+        }
 
         final boolean isRollback = isRollback(session);
 
@@ -911,60 +845,166 @@
                 || apexSessionInfo.isRevertFailed;
     }
 
-    void restoreSession(@NonNull StagedSession session, boolean isDeviceUpgrading) {
-        if (session.hasParentSessionId()) {
-            // Only parent sessions can be restored
-            return;
+    private void handleNonReadyAndDestroyedSessions(List<StagedSession> sessions) {
+        int j = sessions.size();
+        for (int i = 0; i < j; ) {
+            // Maintain following invariant:
+            //  * elements at positions [0, i) should be kept
+            //  * elements at positions [j, n) should be remove.
+            //  * n = sessions.size()
+            StagedSession session = sessions.get(i);
+            if (session.isDestroyed()) {
+                // Device rebooted before abandoned session was cleaned up.
+                session.abandon();
+                StagedSession session2 = sessions.set(j - 1, session);
+                sessions.set(i, session2);
+                j--;
+            } else if (!session.isSessionReady()) {
+                // The framework got restarted before the pre-reboot verification could complete,
+                // restart the verification.
+                mPreRebootVerificationHandler.startPreRebootVerification(session);
+                StagedSession session2 = sessions.set(j - 1, session);
+                sessions.set(i, session2);
+                j--;
+            } else {
+                i++;
+            }
         }
-        // Store this parent session which will be used to check overlapping later
-        createSession(session);
-        // The preconditions used during pre-reboot verification might have changed when device
-        // is upgrading. Updated staged sessions to activation failed before we resume the session.
-        StagedSession sessionToResume = session;
-        if (isDeviceUpgrading && !sessionToResume.isInTerminalState()) {
-            sessionToResume.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Build fingerprint has changed");
-            return;
-        }
-        checkStateAndResume(sessionToResume);
+        // Delete last j elements.
+        sessions.subList(j, sessions.size()).clear();
     }
 
-    private void checkStateAndResume(@NonNull StagedSession session) {
-        // Do not resume session if boot completed already
+    void restoreSessions(@NonNull List<StagedSession> sessions, boolean isDeviceUpgrading) {
+        // Do not resume sessions if boot completed already
         if (SystemProperties.getBoolean("sys.boot_completed", false)) {
             return;
         }
 
-        if (!session.isCommitted()) {
-            // Session hasn't been committed yet, ignore.
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
+            // Quick check that PackageInstallerService gave us sessions we expected.
+            Preconditions.checkArgument(!session.hasParentSessionId(),
+                    session.sessionId() + " is a child session");
+            Preconditions.checkArgument(session.isCommitted(),
+                    session.sessionId() + " is not committed");
+            Preconditions.checkArgument(!session.isInTerminalState(),
+                    session.sessionId() + " is in terminal state");
+            // Store this parent session which will be used to check overlapping later
+            createSession(session);
+        }
+
+        if (isDeviceUpgrading) {
+            // TODO(ioffe): check that corresponding apex sessions are failed.
+            // The preconditions used during pre-reboot verification might have changed when device
+            // is upgrading. Fail all the sessions and exit early.
+            for (int i = 0; i < sessions.size(); i++) {
+                StagedSession session = sessions.get(i);
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Build fingerprint has changed");
+            }
             return;
         }
-        // Check the state of the session and decide what to do next.
-        if (session.isSessionFailed() || session.isSessionApplied()) {
-            // Final states, nothing to do.
+
+        boolean needsCheckpoint = false;
+        boolean supportsCheckpoint = false;
+        try {
+            supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint();
+            needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint();
+        } catch (RemoteException e) {
+            // This means that vold has crashed, and device is in a bad state.
+            throw new IllegalStateException("Failed to get checkpoint status", e);
+        }
+
+        if (sessions.size() > 1 && !supportsCheckpoint) {
+            throw new IllegalStateException("Detected multiple staged sessions on a device without "
+                    + "fs-checkpoint support");
+        }
+
+        // Do a set of quick checks before resuming individual sessions:
+        //   1. Schedule a pre-reboot verification for non-ready sessions.
+        //   2. Abandon destroyed sessions.
+        handleNonReadyAndDestroyedSessions(sessions); // mutates |sessions|
+
+        //   3. Check state of apex sessions is consistent. All non-applied sessions will be marked
+        //      as failed.
+        final SparseArray<ApexSessionInfo> apexSessions = mApexManager.getSessions();
+        boolean hasFailedApexSession = false;
+        boolean hasAppliedApexSession = false;
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
+            if (!session.containsApexSession()) {
+                // At this point we are only interested in apex sessions.
+                continue;
+            }
+            final ApexSessionInfo apexSession = apexSessions.get(session.sessionId());
+            if (apexSession == null || apexSession.isUnknown) {
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, "apexd did "
+                        + "not know anything about a staged session supposed to be activated");
+                continue;
+            } else if (isApexSessionFailed(apexSession)) {
+                hasFailedApexSession = true;
+                String errorMsg = "APEX activation failed. Check logcat messages from apexd "
+                        + "for more information.";
+                if (!TextUtils.isEmpty(apexSession.crashingNativeProcess)) {
+                    prepareForLoggingApexdRevert(session, apexSession.crashingNativeProcess);
+                    errorMsg = "Session reverted due to crashing native process: "
+                            + apexSession.crashingNativeProcess;
+                }
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg);
+                continue;
+            } else if (apexSession.isActivated || apexSession.isSuccess) {
+                hasAppliedApexSession = true;
+                continue;
+            } else if (apexSession.isStaged) {
+                // Apexd did not apply the session for some unknown reason. There is no guarantee
+                // that apexd will install it next time. Safer to proactively mark it as failed.
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Staged session " + session.sessionId() + " at boot didn't activate nor "
+                        + "fail. Marking it as failed anyway.");
+            } else {
+                Slog.w(TAG, "Apex session " + session.sessionId() + " is in impossible state");
+                hasFailedApexSession = true;
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Impossible state");
+            }
+        }
+
+        if (hasAppliedApexSession && hasFailedApexSession) {
+            abortCheckpoint("Found both applied and failed apex sessions", supportsCheckpoint,
+                    needsCheckpoint);
             return;
         }
-        if (session.isDestroyed()) {
-            // Device rebooted before abandoned session was cleaned up.
-            session.abandon();
+
+        if (hasFailedApexSession) {
+            // Either of those means that we failed at least one apex session, hence we should fail
+            // all other sessions.
+            for (int i = 0; i < sessions.size(); i++) {
+                StagedSession session = sessions.get(i);
+                if (session.isSessionFailed()) {
+                    // Session has been already failed in the loop above.
+                    continue;
+                }
+                session.setSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
+                        "Another apex session failed");
+            }
             return;
         }
-        if (!session.isSessionReady()) {
-            // The framework got restarted before the pre-reboot verification could complete,
-            // restart the verification.
-            mPreRebootVerificationHandler.startPreRebootVerification(session);
-        } else {
-            // Session had already being marked ready. Start the checks to verify if there is any
-            // follow-up work.
+
+        // Time to resume sessions.
+        for (int i = 0; i < sessions.size(); i++) {
+            StagedSession session = sessions.get(i);
             try {
-                resumeSession(session);
+                resumeSession(session, supportsCheckpoint, needsCheckpoint);
             } catch (PackageManagerException e) {
-                onInstallationFailure(session, e);
+                onInstallationFailure(session, e, supportsCheckpoint, needsCheckpoint);
             } catch (Exception e) {
                 Slog.e(TAG, "Staged install failed due to unhandled exception", e);
                 onInstallationFailure(session, new PackageManagerException(
                         SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
-                        "Staged install failed due to unhandled exception: " + e));
+                        "Staged install failed due to unhandled exception: " + e),
+                        supportsCheckpoint, needsCheckpoint);
             }
         }
     }
@@ -992,9 +1032,7 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context ctx, Intent intent) {
-                mPreRebootVerificationHandler.readyToStart();
-                BackgroundThread.getExecutor().execute(
-                        () -> logFailedApexSessionsIfNecessary());
+                onBootCompletedBroadcastReceived();
                 ctx.unregisterReceiver(this);
             }
         }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
@@ -1002,6 +1040,12 @@
         mFailureReasonFile.delete();
     }
 
+    @VisibleForTesting
+    void onBootCompletedBroadcastReceived() {
+        mPreRebootVerificationHandler.readyToStart();
+        BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
+    }
+
     private static class LocalIntentReceiverSync {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
@@ -1286,9 +1330,8 @@
         private void handlePreRebootVerification_End(@NonNull StagedSession session) {
             // Before marking the session as ready, start checkpoint service if available
             try {
-                IStorageManager storageManager = PackageHelper.getStorageManager();
-                if (storageManager.supportsCheckpoint()) {
-                    storageManager.startCheckpoint(2);
+                if (PackageHelper.getStorageManager().supportsCheckpoint()) {
+                    PackageHelper.getStorageManager().startCheckpoint(2);
                 }
             } catch (Exception e) {
                 // Failed to get hold of StorageManager
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 139654e..3576950 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -587,7 +587,7 @@
     private static final int TRON_COMPILATION_REASON_ERROR = 0;
     private static final int TRON_COMPILATION_REASON_UNKNOWN = 1;
     private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2;
-    private static final int TRON_COMPILATION_REASON_BOOT = 3;
+    private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3;
     private static final int TRON_COMPILATION_REASON_INSTALL = 4;
     private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5;
     private static final int TRON_COMPILATION_REASON_AB_OTA = 6;
@@ -605,6 +605,8 @@
     private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18;
     private static final int
             TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19;
+    private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20;
+    private static final int TRON_COMPILATION_REASON_POST_BOOT = 21;
 
     // The annotation to add as a suffix to the compilation reason when dexopt was
     // performed with dex metadata.
@@ -618,7 +620,8 @@
             case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN;
             case "error" : return TRON_COMPILATION_REASON_ERROR;
             case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
-            case "boot" : return TRON_COMPILATION_REASON_BOOT;
+            case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+            case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
             case "install" : return TRON_COMPILATION_REASON_INSTALL;
             case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
             case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
new file mode 100644
index 0000000..6cdd4df
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.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 com.android.server.pm.parsing.library;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+/**
+ * Updates a package to remove dependency on android.net.ipsec.ike library.
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class AndroidNetIpSecIkeUpdater extends PackageSharedLibraryUpdater {
+
+    private static final String LIBRARY_NAME = "android.net.ipsec.ike";
+
+    @Override
+    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+        removeLibrary(parsedPackage, LIBRARY_NAME);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index 1405a7d..8a8a302 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -45,6 +45,9 @@
     static {
         final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
 
+        // Remove android.net.ipsec.ike library, it is added to boot classpath since Android S.
+        packageUpdaters.add(new AndroidNetIpSecIkeUpdater());
+
         // Remove com.google.android.maps library.
         packageUpdaters.add(new ComGoogleAndroidMapsUpdater());
 
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 37dfea4..5ee612b 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -125,8 +125,10 @@
     public static void validatePackageDexMetadata(AndroidPackage pkg)
             throws PackageParserException {
         Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
+        String packageName = pkg.getPackageName();
+        long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
         for (String dexMetadata : apkToDexMetadataList) {
-            DexMetadataHelper.validateDexMetadataFile(dexMetadata);
+            DexMetadataHelper.validateDexMetadataFile(dexMetadata, packageName, versionCode);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 0e88862..e05ef48 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,3 @@
-moltmann@google.com
 zhanghai@google.com
 per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
 per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
@@ -7,5 +6,4 @@
 per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
 per-file DefaultPermissionGrantPolicy.java = patb@google.com
 per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
-per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
 per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 30c334d..32bee58 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Permission definition.
@@ -345,6 +346,14 @@
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
     }
 
+    public boolean isKnownSigner() {
+        return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
+    }
+
+    public Set<String> getKnownCerts() {
+        return mPermissionInfo.knownCerts;
+    }
+
     public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
         if (!oldPackageName.equals(mPermissionInfo.packageName)) {
             return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index aff87111..71e53d9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2590,8 +2590,10 @@
         boolean runtimePermissionsRevoked = false;
         int[] updatedUserIds = EMPTY_INT_ARRAY;
 
+        ArraySet<String> isPrivilegedPermissionAllowlisted = null;
         ArraySet<String> shouldGrantSignaturePermission = null;
         ArraySet<String> shouldGrantInternalPermission = null;
+        ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
         final List<String> requestedPermissions = pkg.getRequestedPermissions();
         final int requestedPermissionsSize = requestedPermissions.size();
         for (int i = 0; i < requestedPermissionsSize; i++) {
@@ -2604,15 +2606,24 @@
             if (permission == null) {
                 continue;
             }
-            if (permission.isSignature() && (shouldGrantSignaturePermission(pkg, permission)
-                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission))) {
+            if (permission.isPrivileged()
+                    && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+                if (isPrivilegedPermissionAllowlisted == null) {
+                    isPrivilegedPermissionAllowlisted = new ArraySet<>();
+                }
+                isPrivilegedPermissionAllowlisted.add(permissionName);
+            }
+            if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
+                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted))) {
                 if (shouldGrantSignaturePermission == null) {
                     shouldGrantSignaturePermission = new ArraySet<>();
                 }
                 shouldGrantSignaturePermission.add(permissionName);
             }
             if (permission.isInternal()
-                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission)) {
+                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted)) {
                 if (shouldGrantInternalPermission == null) {
                     shouldGrantInternalPermission = new ArraySet<>();
                 }
@@ -2830,14 +2841,22 @@
 
                     if ((bp.isNormal() && shouldGrantNormalPermission)
                             || (bp.isSignature()
-                                    && ((shouldGrantSignaturePermission != null
-                                            && shouldGrantSignaturePermission.contains(permName))
-                                            || ((bp.isDevelopment() || bp.isRole())
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantSignaturePermission,
+                                            permName)
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))
                             || (bp.isInternal()
-                                    && ((shouldGrantInternalPermission != null
-                                            && shouldGrantInternalPermission.contains(permName))
-                                            || ((bp.isDevelopment() || bp.isRole())
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantInternalPermission,
+                                            permName)
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
                                                     && origState.isPermissionGranted(permName))))) {
                         // Grant an install permission.
                         if (uidState.grantPermission(bp)) {
@@ -3343,7 +3362,100 @@
         return allowed;
     }
 
-    private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+    private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+            @NonNull PackageSetting packageSetting, @NonNull Permission permission) {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true;
+        }
+        final String packageName = pkg.getPackageName();
+        if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        if (!pkg.isPrivileged()) {
+            return true;
+        }
+        if (!Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        final String permissionName = permission.getName();
+        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+            return true;
+        }
+        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+            return false;
+        }
+        // Updated system apps do not need to be allowlisted
+        if (packageSetting.getPkgState().isUpdatedSystemApp()) {
+            // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
+            // can be granted, because an updated system app may be in a shared UID, and in case a
+            // new privileged permission is requested by the updated system app but not the factory
+            // app, although this app and permission combination isn't in the allowlist and can't
+            // get the permission this way, other apps in the shared UID may still get it. A proper
+            // fix for this would be to perform the reconciliation by UID, but for now let's keep
+            // the old workaround working, which is to keep granted privileged permissions still
+            // granted.
+            return true;
+        }
+        // Only enforce the allowlist on boot
+        if (!mSystemReady) {
+            final ApexManager apexManager = ApexManager.getInstance();
+            final String containingApexPackageName =
+                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
+            final boolean isInUpdatedApex = containingApexPackageName != null
+                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+                    MATCH_ACTIVE_PACKAGE));
+            // Apps that are in updated apexs' do not need to be allowlisted
+            if (!isInUpdatedApex) {
+                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+                        + packageName + " (" + pkg.getPath()
+                        + ") not in privapp-permissions allowlist");
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    synchronized (mLock) {
+                        if (mPrivappPermissionsViolations == null) {
+                            mPrivappPermissionsViolations = new ArraySet<>();
+                        }
+                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+                                + permissionName);
+                    }
+                }
+            }
+        }
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+    }
+
+    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
             @NonNull Permission bp) {
         // expect single system package
         String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
@@ -3371,10 +3483,10 @@
     }
 
     private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
-            @NonNull PackageSetting pkgSetting, @NonNull Permission bp) {
+            @NonNull PackageSetting pkgSetting, @NonNull Permission bp,
+            @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) {
         boolean allowed = false;
-        final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
-        final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+        final boolean isPrivilegedPermission = bp.isPrivileged();
         final boolean isOemPermission = bp.isOem();
         if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
             final String permissionName = bp.getName();
@@ -3384,21 +3496,27 @@
                 final PackageSetting disabledPs = mPackageManagerInt
                         .getDisabledSystemPackage(pkg.getPackageName());
                 final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
-                if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
-                        permissionName)) {
-                    allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
-                            true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
-                            permissionName));
+                if (disabledPkg != null
+                        && ((isPrivilegedPermission && disabledPkg.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(disabledPkg,
+                                permissionName)))) {
+                    if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
+                        allowed = true;
+                    } else {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName);
+                    }
                 }
             } else {
-                allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+                allowed = (isPrivilegedPermission && pkg.isPrivileged())
                         || (isOemPermission && canGrantOemPermission(pkg, permissionName));
             }
             // In any case, don't grant a privileged permission to privileged vendor apps, if
             // the permission's protectionLevel does not have the extra 'vendorPrivileged'
             // flag.
-            if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission
-                    && pkg.isVendor()) {
+            if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
                 Slog.w(TAG, "Permission " + permissionName
                         + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
                         + " because it isn't a 'vendorPrivileged' permission.");
@@ -3438,6 +3556,11 @@
             // Any pre-installed system app is allowed to get this permission.
             allowed = true;
         }
+        if (!allowed && bp.isKnownSigner()) {
+            // If the permission is to be granted to a known signer then check if any of this
+            // app's signing certificates are in the trusted certificate digest Set.
+            allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+        }
         // Deferred to be checked under permission data lock inside restorePermissionState().
         //if (!allowed && bp.isDevelopment()) {
         //    // For development permissions, a development permission
@@ -3536,90 +3659,6 @@
         return mPackageManagerInt.getPackageSetting(sourcePackageName);
     }
 
-    private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
-            boolean isUpdatedSystemApp, @NonNull Permission permission) {
-        if (!pkg.isPrivileged()) {
-            return false;
-        }
-        final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
-                permission.getPackageName());
-        if (!isPlatformPermission) {
-            return true;
-        }
-        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
-            return true;
-        }
-        final String permissionName = permission.getName();
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
-            return true;
-        }
-        // Only enforce the allowlist on boot
-        if (!mSystemReady
-                // Updated system apps do not need to be allowlisted
-                && !isUpdatedSystemApp) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String packageName = pkg.getPackageName();
-            final String containingApexPackageName =
-                    apexManager.getActiveApexPackageNameContainingPackage(packageName);
-            final boolean isInUpdatedApex = containingApexPackageName != null
-                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
-                    MATCH_ACTIVE_PACKAGE));
-            // Apps that are in updated apexs' do not need to be allowlisted
-            if (!isInUpdatedApex) {
-                // it's only a reportable violation if the permission isn't explicitly
-                // denied
-                if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
-                    return false;
-                }
-                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
-                        + packageName + " (" + pkg.getPath()
-                        + ") not in privapp-permissions allowlist");
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    synchronized (mLock) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
-                                + permissionName);
-                    }
-                }
-            }
-        }
-        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
-    }
-
-    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
-    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
-        }
-        return permissions != null && permissions.contains(permission);
-    }
-
     private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
         if (!pkg.isOem()) {
             return false;
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 36efb39..080de73 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
@@ -21,11 +21,8 @@
 import android.compat.annotation.EnabledSince;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedIntentInfo;
-import android.os.Binder;
 import android.os.Build;
 import android.util.ArraySet;
 import android.util.Patterns;
@@ -36,19 +33,31 @@
 
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class DomainVerificationCollector {
 
+    // The default domain name matcher doesn't account for wildcards, so prefix with *.
+    private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
+            Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
+
     @NonNull
     private final PlatformCompat mPlatformCompat;
 
     @NonNull
     private final SystemConfig mSystemConfig;
 
+    @NonNull
+    private final Matcher mDomainMatcher;
+
     public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
             @NonNull SystemConfig systemConfig) {
         mPlatformCompat = platformCompat;
         mSystemConfig = systemConfig;
+
+        // Cache the matcher to avoid calling into native on each check
+        mDomainMatcher = DOMAIN_NAME_WITH_WILDCARD.matcher("");
     }
 
     /**
@@ -144,7 +153,10 @@
                 if (intent.handlesWebUris(false)) {
                     int authorityCount = intent.countDataAuthorities();
                     for (int index = 0; index < authorityCount; index++) {
-                        domains.add(intent.getDataAuthority(index).getHost());
+                        String host = intent.getDataAuthority(index).getHost();
+                        if (isValidHost(host)) {
+                            domains.add(host);
+                        }
                     }
                 }
             }
@@ -188,13 +200,22 @@
                 int authorityCount = intent.countDataAuthorities();
                 for (int index = 0; index < authorityCount; index++) {
                     String host = intent.getDataAuthority(index).getHost();
-                    // It's easy to misconfigure autoVerify intent filters, so to avoid
-                    // adding unintended hosts, check if the host is an HTTP domain.
-                    if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+                    if (isValidHost(host)) {
                         domains.add(host);
                     }
                 }
             }
         }
     }
+
+    /**
+     * It's easy to mis-configure autoVerify intent filters, so to avoid adding unintended hosts,
+     * check if the host is an HTTP domain. This applies for both legacy and modern versions of
+     * the API, which will strip invalid hosts from the legacy parsing result. This is done to
+     * improve the reliability of any legacy verifiers.
+     */
+    private boolean isValidHost(String host) {
+        mDomainMatcher.reset(host);
+        return mDomainMatcher.matches();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
index 1925590..b3108c5 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -38,8 +38,6 @@
 import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 
 import java.util.Arrays;
-import java.util.Objects;
-import java.util.Set;
 import java.util.function.Function;
 
 @SuppressWarnings("PointlessBooleanExpression")
@@ -202,8 +200,7 @@
                 printedHeader = true;
             }
 
-            boolean isLinkHandlingAllowed = userState == null
-                    || !userState.isDisallowLinkHandling();
+            boolean isLinkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
 
             writer.increaseIndent();
             writer.print("User ");
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index c521f82..275dd053 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -18,8 +18,10 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Process;
 
@@ -30,10 +32,17 @@
     @NonNull
     private final Context mContext;
 
+    @NonNull
+    private Callback mCallback;
+
     public DomainVerificationEnforcer(@NonNull Context context) {
         mContext = context;
     }
 
+    public void setCallback(@NonNull Callback callback) {
+        mCallback = callback;
+    }
+
     /**
      * Enforced when mutating any state from shell or internally in the system process.
      */
@@ -67,6 +76,11 @@
                             "Caller " + callingUid
                                     + " is not allowed to query domain verification state");
                 }
+
+                mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+                        Binder.getCallingPid(), callingUid,
+                        "Caller " + callingUid + " does not hold "
+                                + android.Manifest.permission.QUERY_ALL_PACKAGES);
                 break;
         }
     }
@@ -84,28 +98,42 @@
                 isAllowed = true;
                 break;
             default:
-                // TODO(b/159952358): Remove permission check? The component package should
-                //  have been checked when the verifier component was first scanned in PMS.
-                mContext.enforcePermission(
-                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
-                        Binder.getCallingPid(), callingUid,
-                        "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
+                final int callingPid = Binder.getCallingPid();
+                boolean isLegacyVerificationAgent = false;
+                if (mContext.checkPermission(
+                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid,
+                        callingUid) != PackageManager.PERMISSION_GRANTED) {
+                    isLegacyVerificationAgent = mContext.checkPermission(
+                            android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+                            callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+                    if (!isLegacyVerificationAgent) {
+                        throw new SecurityException("Caller " + callingUid + " does not hold "
+                                + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
+                    }
+                }
+
+                // If the caller isn't a legacy verifier, it needs the QUERY_ALL permission
+                if (!isLegacyVerificationAgent) {
+                    mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+                            callingPid, callingUid, "Caller " + callingUid + " does not hold "
+                                    + android.Manifest.permission.QUERY_ALL_PACKAGES);
+                }
+
                 isAllowed = proxy.isCallerVerifier(callingUid);
                 break;
         }
 
         if (!isAllowed) {
             throw new SecurityException("Caller " + callingUid
-                    + " is not the approved domain verification agent, isVerifier = "
-                    + proxy.isCallerVerifier(callingUid));
+                    + " is not the approved domain verification agent");
         }
     }
 
     /**
      * Enforced when mutating user selection state inside an exposed API method.
      */
-    public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
-            @UserIdInt int targetUserId) throws SecurityException {
+    public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+            @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
         if (callingUserId != targetUserId) {
             mContext.enforcePermission(
                     Manifest.permission.INTERACT_ACROSS_USERS,
@@ -117,12 +145,51 @@
                 android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
                 Binder.getCallingPid(), callingUid,
                 "Caller is not allowed to edit user selections");
+
+        if (packageName == null) {
+            return true;
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
     }
 
-    public void callerIsLegacyUserSelector(int callingUid) {
+    public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId,
+            @NonNull String packageName, @UserIdInt int targetUserId) {
         mContext.enforcePermission(
                 android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
                 Binder.getCallingPid(), callingUid,
                 "Caller is not allowed to edit user state");
+
+        if (callingUserId != targetUserId) {
+            if (mContext.checkPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) {
+                // Legacy API did not enforce this, so for backwards compatibility, fail silently
+                return false;
+            }
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+    }
+
+    public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId,
+            @NonNull String packageName, @UserIdInt int targetUserId) {
+        if (callingUserId != targetUserId) {
+            // The legacy API enforces the _FULL variant, so maintain that here
+            mContext.enforcePermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    Binder.getCallingPid(), callingUid,
+                    "Caller is not allowed to edit other users");
+        }
+
+        return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
+    }
+
+    public interface Callback {
+        /**
+         * @return true if access to the given package should be filtered and the method failed as
+         * if the package was not installed
+         */
+        boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 0474d78..5d4370a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -16,18 +16,22 @@
 
 package com.android.server.pm.verify.domain;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.os.Binder;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 
@@ -39,6 +43,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
@@ -48,6 +53,78 @@
     UUID DISABLED_ID = new UUID(0, 0);
 
     /**
+     * The app has not been approved for this domain and should never be able to open it through
+     * an implicit web intent.
+     */
+    int APPROVAL_LEVEL_NONE = 0;
+
+    /**
+     * The app has been approved through the legacy
+     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} and
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK}.
+     *
+     * This should be used as the cutoff for showing a picker if no better approved app exists
+     * during the legacy transition period.
+     *
+     * TODO(b/177923646): The legacy values can be removed once the Settings API changes are
+     *  shipped. These values are not stable, so just deleting the constant and shifting others is
+     *  fine.
+     */
+    int APPROVAL_LEVEL_LEGACY_ASK = 1;
+
+    /**
+     * The app has been approved through the legacy
+     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
+     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
+     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}.
+     */
+    int APPROVAL_LEVEL_LEGACY_ALWAYS = 1;
+
+    /**
+     * The app has been chosen by the user through
+     * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
+     * choice to use this app to open an unverified domain.
+     */
+    int APPROVAL_LEVEL_SELECTION = 2;
+
+    /**
+     * The app is approved through the digital asset link statement being hosted at the domain
+     * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
+     * the domain verification agent on device.
+     */
+    int APPROVAL_LEVEL_VERIFIED = 3;
+
+    /**
+     * The app has been installed as an instant app, which grants it total authority on the domains
+     * that it declares. It is expected that the package installer validate the domains the app
+     * declares against the digital asset link statements before allowing it to be installed.
+     *
+     * The user is still able to disable instant app link handling through
+     * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+     */
+    int APPROVAL_LEVEL_INSTANT_APP = 4;
+
+    /**
+     * Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
+     * which sorts packages by approval priority. A higher numerical value means the package should
+     * override all lower values. This means that comparison using less/greater than IS valid.
+     *
+     * Negative values are possible, although not implemented, reserved if explicit disable of a
+     * package for a domain needs to be tracked.
+     */
+    @IntDef({
+            APPROVAL_LEVEL_NONE,
+            APPROVAL_LEVEL_LEGACY_ASK,
+            APPROVAL_LEVEL_LEGACY_ALWAYS,
+            APPROVAL_LEVEL_SELECTION,
+            APPROVAL_LEVEL_VERIFIED,
+            APPROVAL_LEVEL_INSTANT_APP
+    })
+    @interface ApprovalLevel{}
+
+    /**
      * Generate a new domain set ID to be used for attaching new packages.
      */
     @NonNull
@@ -174,8 +251,10 @@
      * Set aside a legacy user selection that will be restored to a pending
      * {@link DomainVerificationPkgState} once it's added through
      * {@link #addPackage(PackageSetting)}.
+     *
+     * @return true if state changed successfully
      */
-    void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+    boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
 
     /**
      * Until the legacy APIs are entirely removed, returns the legacy state from the previously
@@ -184,12 +263,6 @@
     int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
 
     /**
-     * Serialize a legacy setting that wasn't attached yet.
-     * TODO: Does this even matter? Should consider for removal.
-     */
-    void writeLegacySettings(TypedXmlSerializer serializer, String name);
-
-    /**
      * Print the verification state and user selection state of a package.
      *
      * @param packageName        the package whose state to change, or all packages if none is
@@ -215,11 +288,28 @@
     DomainVerificationCollector getCollector();
 
     /**
-     * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
-     * This can be because the domain was auto-verified for the package, or if the user manually
-     * chose to enable the domain for the package.
+     * Filters the provided list down to the {@link ResolveInfo} objects that should be allowed
+     * to open the domain inside the {@link Intent}. It is possible for no packages represented in
+     * the list to be approved, in which case an empty list will be returned.
+     *
+     * @return the filtered list and the corresponding approval level
      */
-    boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+    @NonNull
+    Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction);
+
+    /**
+     * Check at what precedence a package resolving a URI is approved to takeover the domain.
+     * This can be because the domain was auto-verified for the package, or if the user manually
+     * chose to enable the domain for the package. If an app is auto-verified, it will be
+     * preferred over apps that were manually selected.
+     *
+     * NOTE: This should not be used for filtering intent resolution. See
+     * {@link #filterToApprovedApp(Intent, List, int, Function)} for that.
+     */
+    @ApprovalLevel
+    int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
             @UserIdInt int userId);
 
     /**
@@ -235,7 +325,7 @@
             throws IllegalArgumentException, NameNotFoundException;
 
 
-    interface Connection extends Function<String, PackageSetting> {
+    interface Connection extends DomainVerificationEnforcer.Callback {
 
         /**
          * Notify that a settings change has been made and that eventually
@@ -268,10 +358,5 @@
 
         @Nullable
         AndroidPackage getPackageLocked(@NonNull String pkgName);
-
-        @Override
-        default PackageSetting apply(@NonNull String pkgName) {
-            return getPackageSettingLocked(pkgName);
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
index 679f948..c864b29 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -53,7 +53,7 @@
 
     public static final String TAG_USER_STATE = "user-state";
     public static final String ATTR_USER_ID = "userId";
-    public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+    public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
     public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
     public static final String TAG_HOST = "host";
 
@@ -252,7 +252,7 @@
             return null;
         }
 
-        boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+        boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
         ArraySet<String> enabledHosts = new ArraySet<>();
 
         SettingsXml.ChildSection child = section.children();
@@ -260,7 +260,7 @@
             readEnabledHosts(child, enabledHosts);
         }
 
-        return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+        return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
     }
 
     private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -279,8 +279,8 @@
         try (SettingsXml.WriteSection section =
                      parentSection.startSection(TAG_USER_STATE)
                              .attribute(ATTR_USER_ID, userState.getUserId())
-                             .attribute(ATTR_DISALLOW_LINK_HANDLING,
-                                     userState.isDisallowLinkHandling())) {
+                             .attribute(ATTR_ALLOW_LINK_HANDLING,
+                                     userState.isLinkHandlingAllowed())) {
             ArraySet<String> enabledHosts = userState.getEnabledHosts();
             if (!enabledHosts.isEmpty()) {
                 try (SettingsXml.WriteSection enabledHostsSection =
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 e24e5bb..86a92d79 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
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.verify.domain;
 
+import static java.util.Collections.emptyList;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -26,6 +28,8 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
@@ -35,6 +39,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -47,12 +52,12 @@
 import com.android.server.SystemService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.PackageSetting;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
 import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
 import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -62,6 +67,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
@@ -92,9 +98,9 @@
      * immediately attached once its available.
      * <p>
      * Generally this should be not accessed directly. Prefer calling {@link
-     * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+     * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}.
      *
-     * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+     * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)
      **/
     @GuardedBy("mLock")
     @NonNull
@@ -160,6 +166,7 @@
     @Override
     public void setConnection(@NonNull Connection connection) {
         mConnection = connection;
+        mEnforcer.setCallback(mConnection);
     }
 
     @NonNull
@@ -285,7 +292,7 @@
         mEnforcer.assertApprovedVerifier(callingUid, mProxy);
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
-                    true /* forAutoVerify */);
+                    true /* forAutoVerify */, callingUid, null /* userId */);
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
             for (String domain : domains) {
                 Integer previousState = stateMap.get(domain);
@@ -389,8 +396,10 @@
 
     public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
             boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
+        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+        }
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
@@ -398,7 +407,7 @@
             }
 
             pkgState.getOrCreateUserSelectionState(userId)
-                    .setDisallowLinkHandling(!allowed);
+                    .setLinkHandlingAllowed(allowed);
         }
 
         mConnection.scheduleWriteSettings();
@@ -420,11 +429,11 @@
                         for (int userStateIndex = 0; userStateIndex < userStatesSize;
                                 userStateIndex++) {
                             userStates.valueAt(userStateIndex)
-                                    .setDisallowLinkHandling(!allowed);
+                                    .setLinkHandlingAllowed(allowed);
                         }
                     } else {
                         pkgState.getOrCreateUserSelectionState(userId)
-                                .setDisallowLinkHandling(!allowed);
+                                .setLinkHandlingAllowed(allowed);
                     }
                 }
 
@@ -437,7 +446,7 @@
                 }
 
                 pkgState.getOrCreateUserSelectionState(userId)
-                        .setDisallowLinkHandling(!allowed);
+                        .setLinkHandlingAllowed(allowed);
             }
         }
 
@@ -455,11 +464,29 @@
     public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
             @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
             throws InvalidDomainSetException, NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
         synchronized (mLock) {
+            final int callingUid = mConnection.getCallingUid();
+            // Pass null for package name here and do the app visibility enforcement inside
+            // getAndValidateAttachedLocked instead, since this has to fail with the same invalid
+            // ID reason if the target app is invisible
+            if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
+                    null /* packageName */, userId)) {
+                throw new InvalidDomainSetException(domainSetId, null,
+                        InvalidDomainSetException.REASON_ID_INVALID);
+            }
+
+            if (enabled) {
+                for (String domain : domains) {
+                    if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
+                            mConnection::getPackageSettingLocked).first.isEmpty()) {
+                        throw new InvalidDomainSetException(domainSetId, null,
+                                InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
+                    }
+                }
+            }
+
             DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
-                    false /* forAutoVerify */);
+                    false /* forAutoVerify */, callingUid, userId);
             DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
             if (enabled) {
                 userState.addHosts(domains);
@@ -556,8 +583,10 @@
     @Override
     public DomainVerificationUserSelection getDomainVerificationUserSelection(
             @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
-        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
-                mConnection.getCallingUserId(), userId);
+        if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+        }
         synchronized (mLock) {
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
@@ -577,10 +606,10 @@
                 hostToUserSelectionMap.put(domains.valueAt(index), false);
             }
 
-            boolean openVerifiedLinks = false;
             DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            boolean linkHandlingAllowed = true;
             if (userState != null) {
-                openVerifiedLinks = !userState.isDisallowLinkHandling();
+                linkHandlingAllowed = userState.isLinkHandlingAllowed();
                 ArraySet<String> enabledHosts = userState.getEnabledHosts();
                 int hostsSize = enabledHosts.size();
                 for (int index = 0; index < hostsSize; index++) {
@@ -589,7 +618,7 @@
             }
 
             return new DomainVerificationUserSelection(pkgState.getId(), packageName,
-                    UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+                    UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
         }
     }
 
@@ -671,7 +700,7 @@
                     ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
                     newEnabledHosts.retainAll(newWebDomains);
                     DomainVerificationUserState newUserState = new DomainVerificationUserState(
-                            userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+                            userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
                     newUserStates.put(userId, newUserState);
                 }
             }
@@ -844,23 +873,27 @@
     }
 
     @Override
-    public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
-        mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+    public boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId,
+            int state) {
+        if (!mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            return false;
+        }
         mLegacySettings.add(packageName, userId, state);
         mConnection.scheduleWriteSettings();
+        return true;
     }
 
     @Override
     public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+        if (!mEnforcer.callerIsLegacyUserQuerent(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), packageName, userId)) {
+            return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+        }
         return mLegacySettings.getUserState(packageName, userId);
     }
 
     @Override
-    public void writeLegacySettings(TypedXmlSerializer serializer, String name) {
-
-    }
-
-    @Override
     public void clearPackage(@NonNull String packageName) {
         synchronized (mLock) {
             mAttachedPkgStates.remove(packageName);
@@ -894,7 +927,7 @@
         // This method is only used by DomainVerificationShell, which doesn't lock PMS, so it's
         // safe to pass mConnection directly here and lock PMS. This method is not exposed
         // to the general system server/PMS.
-        printState(writer, packageName, userId, mConnection);
+        printState(writer, packageName, userId, mConnection::getPackageSettingLocked);
     }
 
     @Override
@@ -903,7 +936,7 @@
             @Nullable Function<String, PackageSetting> pkgSettingFunction)
             throws NameNotFoundException {
         if (pkgSettingFunction == null) {
-            pkgSettingFunction = mConnection;
+            pkgSettingFunction = mConnection::getPackageSettingLocked;
         }
 
         synchronized (mLock) {
@@ -935,10 +968,14 @@
      * Validates parameters provided by an external caller. Checks that an ID is still live and that
      * any provided domains are valid. Should be called at the beginning of each API that takes in a
      * {@link UUID} domain set ID.
+     *
+     * @param userIdForFilter which user to filter app access to, or null if the caller has already
+     *                        validated package visibility
      */
     @GuardedBy("mLock")
     private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
-            @NonNull Set<String> domains, boolean forAutoVerify)
+            @NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
+            @Nullable Integer userIdForFilter)
             throws InvalidDomainSetException, NameNotFoundException {
         if (domainSetId == null) {
             throw new InvalidDomainSetException(null, null,
@@ -952,6 +989,13 @@
         }
 
         String pkgName = pkgState.getPackageName();
+
+        if (userIdForFilter != null
+                && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
+            throw new InvalidDomainSetException(domainSetId, null,
+                    InvalidDomainSetException.REASON_ID_INVALID);
+        }
+
         PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
         if (pkgSetting == null || pkgSetting.getPkg() == null) {
             throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
@@ -1129,46 +1173,211 @@
         mConnection.scheduleWriteSettings();
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * Resolving an Intent to an approved app happens in stages:
+     * <ol>
+     *     <li>Find all non-zero approved packages for the {@link Intent}'s domain</li>
+     *     <li>Filter to packages with the highest approval level, see {@link ApprovalLevel}</li>
+     *     <li>Filter out {@link ResolveInfo}s that don't match that approved packages</li>
+     *     <li>Take the approved packages with the latest install time</li>
+     *     <li>Take the ResolveInfo representing the Activity declared last in the manifest</li>
+     *     <li>Return remaining results if any exist</li>
+     * </ol>
+     */
+    @NonNull
     @Override
-    public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+    public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
+            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        String domain = intent.getData().getHost();
+
+        // Collect package names
+        ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+        int infosSize = infos.size();
+        for (int index = 0; index < infosSize; index++) {
+            packageApprovals.put(infos.get(index).getComponentInfo().packageName,
+                    APPROVAL_LEVEL_NONE);
+        }
+
+        // Find all approval levels
+        int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+                pkgSettingFunction);
+        if (highestApproval == APPROVAL_LEVEL_NONE) {
+            return Pair.create(emptyList(), highestApproval);
+        }
+
+        // Filter to highest, non-zero packages
+        ArraySet<String> approvedPackages = new ArraySet<>();
+        for (int index = 0; index < infosSize; index++) {
+            if (packageApprovals.valueAt(index) == highestApproval) {
+                approvedPackages.add(packageApprovals.keyAt(index));
+            }
+        }
+
+        ArraySet<String> filteredPackages = new ArraySet<>();
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // To maintain legacy behavior while the Settings API is not implemented,
+            // show the chooser if all approved apps are marked ask, skipping the
+            // last app, last declaration filtering.
+            filteredPackages.addAll(approvedPackages);
+        } else {
+            // Filter to last installed package
+            long latestInstall = Long.MIN_VALUE;
+            int approvedSize = approvedPackages.size();
+            for (int index = 0; index < approvedSize; index++) {
+                String packageName = approvedPackages.valueAt(index);
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+                long installTime = pkgSetting.getFirstInstallTime();
+                if (installTime > latestInstall) {
+                    latestInstall = installTime;
+                    filteredPackages.clear();
+                    filteredPackages.add(packageName);
+                } else if (installTime == latestInstall) {
+                    filteredPackages.add(packageName);
+                }
+            }
+        }
+
+        // Filter to approved ResolveInfos
+        ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
+        for (int index = 0; index < infosSize; index++) {
+            ResolveInfo info = infos.get(index);
+            String packageName = info.getComponentInfo().packageName;
+            if (filteredPackages.contains(packageName)) {
+                List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
+                if (infosPerPackage == null) {
+                    infosPerPackage = new ArrayList<>();
+                    approvedInfos.put(packageName, infosPerPackage);
+                }
+                infosPerPackage.add(info);
+            }
+        }
+
+        List<ResolveInfo> finalList;
+        if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+            // If legacy ask, skip the last declaration filtering
+            finalList = new ArrayList<>();
+            int size = approvedInfos.size();
+            for (int index = 0; index < size; index++) {
+                finalList.addAll(approvedInfos.valueAt(index));
+            }
+        } else {
+            // Find the last declared ResolveInfo per package
+            finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+        }
+
+        return Pair.create(finalList, highestApproval);
+    }
+
+    /**
+     * @return highest approval level found
+     */
+    private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+            @NonNull String domain, @UserIdInt int userId,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = APPROVAL_LEVEL_NONE;
+        int size = inputMap.size();
+        for (int index = 0; index < size; index++) {
+            String packageName = inputMap.keyAt(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+                continue;
+            }
+            int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+            highestApproval = Math.max(highestApproval, approval);
+            inputMap.setValueAt(index, approval);
+        }
+
+        return highestApproval;
+    }
+
+    @NonNull
+    private List<ResolveInfo> filterToLastDeclared(
+            @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
+
+        int inputSize = inputMap.size();
+        for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
+            String packageName = inputMap.keyAt(inputIndex);
+            List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
+            if (pkg == null) {
+                continue;
+            }
+
+            ResolveInfo result = null;
+            int highestIndex = -1;
+            int infosSize = infos.size();
+            for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
+                ResolveInfo info = infos.get(infoIndex);
+                List<ParsedActivity> activities = pkg.getActivities();
+                int activitiesSize = activities.size();
+                for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+                    if (Objects.equals(activities.get(activityIndex).getComponentName(),
+                            info.getComponentInfo().getComponentName())) {
+                        if (activityIndex > highestIndex) {
+                            highestIndex = activityIndex;
+                            result = info;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            // Shouldn't be null, but might as well be safe
+            if (result != null) {
+                finalList.add(result);
+            }
+        }
+
+        return finalList;
+    }
+
+    @Override
+    public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
             @UserIdInt int userId) {
         String packageName = pkgSetting.name;
         if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, intent, userId, false, "not valid intent");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
 
-        String host = intent.getData().getHost();
+        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
+    }
+
+    /**
+     * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
+     *                    otherwise.
+     */
+    private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
+            @UserIdInt int userId, @NonNull Object debugObject) {
+        String packageName = pkgSetting.name;
         final AndroidPackage pkg = pkgSetting.getPkg();
 
         // Should never be null, but if it is, skip this and assume that v2 is enabled
-        if (pkg != null) {
-            // To allow an instant app to immediately open domains after being installed by the
-            // user, auto approve them for any declared autoVerify domains.
-            if (pkgSetting.getInstantApp(userId)
-                    && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
-                return true;
-            }
-
-            if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
-                int legacyState = mLegacySettings.getUserState(packageName, userId);
-                switch (legacyState) {
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
-                        // If nothing specifically set, assume v2 rules
-                        break;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
-                        // With v2 split into 2 lists, always and undefined, the concept of whether
-                        // or not to ask is irrelevant. Assume the user wants this application to
-                        // open the domain.
-                        return true;
-                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
-                        // Never has the same semantics are before
-                        return false;
-                }
+        if (pkg != null && !DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg,
+                SETTINGS_API_V2)) {
+            switch (mLegacySettings.getUserState(packageName, userId)) {
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+                    // If nothing specifically set, assume v2 rules
+                    break;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+                    return APPROVAL_LEVEL_NONE;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+                    return APPROVAL_LEVEL_LEGACY_ASK;
+                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                    return APPROVAL_LEVEL_LEGACY_ALWAYS;
             }
         }
 
@@ -1176,59 +1385,76 @@
             DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
             if (pkgState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "pkgState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+            if (userState != null && !userState.isLinkHandlingAllowed()) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, false,
+                            "link handling not allowed");
+                }
+                return APPROVAL_LEVEL_NONE;
+            }
+
+            // The instant app branch must be run after the link handling check,
+            // since that should also disable instant apps if toggled
+            if (pkg != null) {
+                // To allow an instant app to immediately open domains after being installed by the
+                // user, auto approve them for any declared autoVerify domains.
+                if (pkgSetting.getInstantApp(userId)
+                        && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+                    return APPROVAL_LEVEL_INSTANT_APP;
+                }
             }
 
             ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
-            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            // Check if the exact host matches
+            Integer state = stateMap.get(host);
+            if (state != null && DomainVerificationManager.isStateVerified(state)) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, debugObject, userId, true,
+                            "host verified exactly");
+                }
+                return APPROVAL_LEVEL_VERIFIED;
+            }
 
-            // Only allow autoVerify approval if the user hasn't disabled it
-            if (userState == null || !userState.isDisallowLinkHandling()) {
-                // Check if the exact host matches
-                Integer state = stateMap.get(host);
-                if (state != null && DomainVerificationManager.isStateVerified(state)) {
-                    if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true, "host verified exactly");
-                    }
-                    return true;
+            // Otherwise see if the host matches a verified domain by wildcard
+            int stateMapSize = stateMap.size();
+            for (int index = 0; index < stateMapSize; index++) {
+                if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+                    continue;
                 }
 
-                // Otherwise see if the host matches a verified domain by wildcard
-                int stateMapSize = stateMap.size();
-                for (int index = 0; index < stateMapSize; index++) {
-                    if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
-                        continue;
+                String domain = stateMap.keyAt(index);
+                if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+                    if (DEBUG_APPROVAL) {
+                        debugApproval(packageName, debugObject, userId, true,
+                                "host verified by wildcard");
                     }
-
-                    String domain = stateMap.keyAt(index);
-                    if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
-                        if (DEBUG_APPROVAL) {
-                            debugApproval(packageName, intent, userId, true,
-                                    "host verified by wildcard");
-                        }
-                        return true;
-                    }
+                    return APPROVAL_LEVEL_VERIFIED;
                 }
             }
 
             // Check user state if available
             if (userState == null) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, false, "userState unavailable");
+                    debugApproval(packageName, debugObject, userId, false, "userState unavailable");
                 }
-                return false;
+                return APPROVAL_LEVEL_NONE;
             }
 
             // See if the user has approved the exact host
             ArraySet<String> enabledHosts = userState.getEnabledHosts();
             if (enabledHosts.contains(host)) {
                 if (DEBUG_APPROVAL) {
-                    debugApproval(packageName, intent, userId, true,
+                    debugApproval(packageName, debugObject, userId, true,
                             "host enabled by user exactly");
                 }
-                return true;
+                return APPROVAL_LEVEL_SELECTION;
             }
 
             // See if the host matches a user selection by wildcard
@@ -1237,24 +1463,85 @@
                 String domain = enabledHosts.valueAt(index);
                 if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
                     if (DEBUG_APPROVAL) {
-                        debugApproval(packageName, intent, userId, true,
+                        debugApproval(packageName, debugObject, userId, true,
                                 "host enabled by user through wildcard");
                     }
-                    return true;
+                    return APPROVAL_LEVEL_SELECTION;
                 }
             }
 
             if (DEBUG_APPROVAL) {
-                debugApproval(packageName, intent, userId, false, "not approved");
+                debugApproval(packageName, debugObject, userId, false, "not approved");
             }
-            return false;
+            return APPROVAL_LEVEL_NONE;
         }
     }
 
-    private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+    /**
+     * @return the filtered list paired with the corresponding approval level
+     */
+    @NonNull
+    private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
+            @UserIdInt int userId, int minimumApproval,
+            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+        int highestApproval = minimumApproval;
+        List<String> approvedPackages = emptyList();
+
+        synchronized (mLock) {
+            final int size = mAttachedPkgStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                String packageName = pkgState.getPackageName();
+                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+                if (pkgSetting == null) {
+                    continue;
+                }
+
+                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+                if (level < minimumApproval) {
+                    continue;
+                }
+
+                if (level > highestApproval) {
+                    approvedPackages.clear();
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                    highestApproval = level;
+                } else if (level == highestApproval) {
+                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
+                }
+            }
+        }
+
+        if (approvedPackages.isEmpty()) {
+            return Pair.create(approvedPackages, APPROVAL_LEVEL_NONE);
+        }
+
+        List<String> filteredPackages = new ArrayList<>();
+        long latestInstall = Long.MIN_VALUE;
+        final int approvedSize = approvedPackages.size();
+        for (int index = 0; index < approvedSize; index++) {
+            String packageName = approvedPackages.get(index);
+            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+            if (pkgSetting == null) {
+                continue;
+            }
+            long installTime = pkgSetting.getFirstInstallTime();
+            if (installTime > latestInstall) {
+                latestInstall = installTime;
+                filteredPackages.clear();
+                filteredPackages.add(packageName);
+            } else if (installTime == latestInstall) {
+                filteredPackages.add(packageName);
+            }
+        }
+
+        return Pair.create(filteredPackages, highestApproval);
+    }
+
+    private void debugApproval(@NonNull String packageName, @NonNull Object debugObject,
             @UserIdInt int userId, boolean approved, @NonNull String reason) {
         String approvalString = approved ? "approved" : "denied";
-        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
-                + " for user " + userId + ": " + reason);
+        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+                + debugObject + " for user " + userId + ": " + reason);
     }
 }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
index 073967e..a8e937c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -23,7 +23,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -229,14 +228,14 @@
             DomainVerificationUserState oldUserState =
                     oldSelectionStates.get(UserHandle.USER_SYSTEM);
 
-            boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+            boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
             if (oldUserState == null) {
                 oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
-                        newEnabledHosts, disallowLinkHandling);
+                        newEnabledHosts, linkHandlingAllowed);
                 oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
             } else {
                 oldUserState.addHosts(newEnabledHosts)
-                        .setDisallowLinkHandling(disallowLinkHandling);
+                        .setLinkHandlingAllowed(linkHandlingAllowed);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
index 8e82608..2246864 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -25,10 +25,10 @@
 import java.util.Set;
 
 /**
- * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
- * that domain when a web URL Intent is sent ft.
+ * Tracks which domains have been explicitly enabled by the user, allowing it to open that domain
+ * when a web URL Intent is sent and the application is the highest priority for that domain.
  */
-@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
 public class DomainVerificationUserState {
 
     @UserIdInt
@@ -38,8 +38,10 @@
     @NonNull
     private final ArraySet<String> mEnabledHosts;
 
-    /** Whether to disallow this package from automatically opening links by auto verification. */
-    private boolean mDisallowLinkHandling;
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
+    private boolean mLinkHandlingAllowed = true;
 
     public DomainVerificationUserState(@UserIdInt int userId) {
         mUserId = userId;
@@ -67,13 +69,15 @@
     }
 
 
+
     // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
+    // /verify/domain/models/DomainVerificationUserState.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -85,19 +89,21 @@
      *
      * @param enabledHosts
      *   List of domains which have been enabled by the user. *
+     * @param linkHandlingAllowed
+     *   Whether to allow this package to automatically open links by auto verification.
      */
     @DataClass.Generated.Member
     public DomainVerificationUserState(
             @UserIdInt int userId,
             @NonNull ArraySet<String> enabledHosts,
-            boolean disallowLinkHandling) {
+            boolean linkHandlingAllowed) {
         this.mUserId = userId;
         com.android.internal.util.AnnotationValidations.validate(
                 UserIdInt.class, null, mUserId);
         this.mEnabledHosts = enabledHosts;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mEnabledHosts);
-        this.mDisallowLinkHandling = disallowLinkHandling;
+        this.mLinkHandlingAllowed = linkHandlingAllowed;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -115,14 +121,20 @@
         return mEnabledHosts;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public boolean isDisallowLinkHandling() {
-        return mDisallowLinkHandling;
+    public boolean isLinkHandlingAllowed() {
+        return mLinkHandlingAllowed;
     }
 
+    /**
+     * Whether to allow this package to automatically open links by auto verification.
+     */
     @DataClass.Generated.Member
-    public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
-        mDisallowLinkHandling = value;
+    public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
+        mLinkHandlingAllowed = value;
         return this;
     }
 
@@ -135,7 +147,7 @@
         return "DomainVerificationUserState { " +
                 "userId = " + mUserId + ", " +
                 "enabledHosts = " + mEnabledHosts + ", " +
-                "disallowLinkHandling = " + mDisallowLinkHandling +
+                "linkHandlingAllowed = " + mLinkHandlingAllowed +
         " }";
     }
 
@@ -154,7 +166,7 @@
         return true
                 && mUserId == that.mUserId
                 && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
-                && mDisallowLinkHandling == that.mDisallowLinkHandling;
+                && mLinkHandlingAllowed == that.mLinkHandlingAllowed;
     }
 
     @Override
@@ -166,15 +178,15 @@
         int _hash = 1;
         _hash = 31 * _hash + mUserId;
         _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
-        _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+        _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1608234273324L,
+            time = 1612894390039L,
             codegenVersion = "1.0.22",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
-            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mDisallowLinkHandling\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mLinkHandlingAllowed\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 9389e63..a804065 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -30,7 +30,6 @@
 import android.content.pm.verify.domain.DomainVerificationState;
 import android.os.Process;
 import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -45,6 +44,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -168,22 +168,58 @@
                     return true;
                 }
 
-                Set<String> successfulDomains = new ArraySet<>(info.getHostToStateMap().keySet());
-                successfulDomains.removeAll(response.failedDomains);
+                AndroidPackage pkg = mConnection.getPackage(packageName);
+                if (pkg == null) {
+                    return true;
+                }
+
+                ArraySet<String> failedDomains = new ArraySet<>(response.failedDomains);
+                Map<String, Integer> hostToStateMap = info.getHostToStateMap();
+                Set<String> hostKeySet = hostToStateMap.keySet();
+                ArraySet<String> successfulDomains = new ArraySet<>(hostKeySet);
+                successfulDomains.removeAll(failedDomains);
+
+                // v1 doesn't handle wildcard domains, so check them here for the verifier
+                int size = successfulDomains.size();
+                for (int index = size - 1; index >= 0; index--) {
+                    String domain = successfulDomains.valueAt(index);
+                    if (domain.startsWith("*.")) {
+                        String nonWildcardDomain = domain.substring(2);
+                        if (failedDomains.contains(nonWildcardDomain)) {
+                            failedDomains.add(domain);
+                            successfulDomains.removeAt(index);
+
+                            // It's possible to declare a wildcard without declaring its
+                            // non-wildcard equivalent, so if it wasn't originally declared,
+                            // remove the transformed domain from the failed set. Otherwise the
+                            // manager will not accept the failed set as it contains an undeclared
+                            // domain.
+                            if (!hostKeySet.contains(nonWildcardDomain)) {
+                                failedDomains.remove(nonWildcardDomain);
+                            }
+                        }
+                    }
+                }
 
                 int callingUid = response.callingUid;
-                try {
-                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
-                            successfulDomains, DomainVerificationState.STATE_SUCCESS);
-                } catch (DomainVerificationManager.InvalidDomainSetException
-                        | PackageManager.NameNotFoundException ignored) {
+                if (!successfulDomains.isEmpty()) {
+                    try {
+                        mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                                successfulDomains, DomainVerificationState.STATE_SUCCESS);
+                    } catch (DomainVerificationManager.InvalidDomainSetException
+                            | PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
+                    }
                 }
-                try {
-                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
-                            new ArraySet<>(response.failedDomains),
-                            DomainVerificationState.STATE_LEGACY_FAILURE);
-                } catch (DomainVerificationManager.InvalidDomainSetException
-                        | PackageManager.NameNotFoundException ignored) {
+
+                if (!failedDomains.isEmpty()) {
+                    try {
+                        mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                                failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
+                    } catch (DomainVerificationManager.InvalidDomainSetException
+                            | PackageManager.NameNotFoundException e) {
+                        Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
+                    }
                 }
 
                 return true;
@@ -235,7 +271,21 @@
         // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
         // not the version of the verification agent on device.
         ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
-        return TextUtils.join(" ", domains);
+
+        // v1 doesn't handle wildcard domains, so transform them here to the root
+        StringBuilder builder = new StringBuilder();
+        int size = domains.size();
+        for (int index = 0; index < size; index++) {
+            if (index > 0) {
+                builder.append(" ");
+            }
+            String domain = domains.valueAt(index);
+            if (domain.startsWith("*.")) {
+                domain = domain.substring(2);
+            }
+            builder.append(domain);
+        }
+        return builder.toString();
     }
 
     private static class Response {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index ac358db..4e1065a 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.policy;
 
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -84,7 +85,8 @@
     private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
 
     @VisibleForTesting
-    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
+            "DEFAULT");
 
     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc81961..6a441f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -346,8 +346,21 @@
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
-    /** Amount of time (in milliseconds) a toast window can be shown. */
-    public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds
+    /**
+      * Extra time for additional SystemUI animations.
+      * <p>Since legacy apps can add Toast windows directly instead of using Toast APIs,
+      * {@link DisplayPolicy} ensures that the window manager removes toast windows after
+      * TOAST_WINDOW_TIMEOUT. We increase this timeout by TOAST_WINDOW_ANIM_BUFFER to account for
+      * SystemUI's in/out toast animations, so that the toast text is still shown for a minimum
+      * of 3.5 seconds and the animations are finished before window manager removes the window.
+      */
+    public static final int TOAST_WINDOW_ANIM_BUFFER = 600;
+
+    /**
+      * Amount of time (in milliseconds) a toast window can be shown before it's automatically
+      * removed by window manager.
+      */
+    public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER;
 
     /**
      * Lock protecting internal state.  Must not call out into window
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8e0d632..c0b8202 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -90,11 +90,11 @@
 import android.view.Display;
 import android.view.KeyEvent;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
@@ -5138,10 +5138,15 @@
             // Get current time before acquiring the lock so that the calculated end time is as
             // accurate as possible.
             final long nowElapsed = SystemClock.elapsedRealtime();
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.BATTERY_PREDICTION)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, "setBatteryDischargePrediction");
+            }
 
             final long timeRemainingMs = timeRemaining.getDuration().toMillis();
-                // A non-positive number means the battery should be dead right now...
+            // A non-positive number means the battery should be dead right now...
             Preconditions.checkArgumentPositive(timeRemainingMs,
                     "Given time remaining is not positive: " + timeRemainingMs);
 
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ea41980..b7285d5 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -73,6 +73,8 @@
     private TimerTrigger mTimerTrigger;
     @Nullable
     private StatsPullAtomCallbackImpl mPullAtomCallback;
+    @Nullable
+    private PowerStatsInternal mPowerStatsInternal;
 
     @VisibleForTesting
     static class Injector {
@@ -125,8 +127,8 @@
         }
 
         StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
-                IPowerStatsHALWrapper powerStatsHALWrapper) {
-            return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+                PowerStatsInternal powerStatsInternal) {
+            return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
         }
     }
 
@@ -175,21 +177,14 @@
     @Override
     public void onStart() {
         if (getPowerStatsHal().isInitialized()) {
-            // Only create internal service if PowerStatsHal is available.
-            publishLocalService(PowerStatsInternal.class, new LocalService());
+            mPowerStatsInternal = new LocalService();
+            publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
         }
         publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
     }
 
     private void onSystemServicesReady() {
-        if (getPowerStatsHal().isInitialized()) {
-            if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
-
-            // Only start statsd pullers if initialization is successful.
-            mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
-        } else {
-            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
-        }
+        mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
     }
 
     private void onBootCompleted() {
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
index 7c6999a..bdabefb 100644
--- a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -24,26 +24,31 @@
 import android.hardware.power.stats.State;
 import android.hardware.power.stats.StateResidency;
 import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.Slog;
 import android.util.StatsEvent;
 
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 /**
  * StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
  * SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
  */
 public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = StatsPullAtomCallbackImpl.class.getSimpleName();
     private Context mContext;
-    private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private PowerStatsInternal mPowerStatsInternal;
     private Map<Integer, Channel> mChannels = new HashMap();
     private Map<Integer, String> mEntityNames = new HashMap();
-    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+    private static final int STATS_PULL_TIMEOUT_MILLIS = 2000;
+    private static final boolean DEBUG = false;
 
     @Override
     public int onPullAtom(int atomTag, List<StatsEvent> data) {
@@ -57,21 +62,28 @@
         }
     }
 
-    private void initPullOnDevicePowerMeasurement() {
-        Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
-        if (channels == null) {
-            return;
+    private boolean initPullOnDevicePowerMeasurement() {
+        Channel[] channels = mPowerStatsInternal.getEnergyMeterInfo();
+        if (channels == null || channels.length == 0) {
+            Slog.e(TAG, "Failed to init OnDevicePowerMeasurement puller");
+            return false;
         }
 
         for (int i = 0; i < channels.length; i++) {
             final Channel channel = channels[i];
             mChannels.put(channel.id, channel);
         }
+
+        return true;
     }
 
     private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
-        EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
-        if (energyMeasurements == null) {
+        final EnergyMeasurement[] energyMeasurements;
+        try {
+            energyMeasurements = mPowerStatsInternal.readEnergyMeterAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to readEnergyMeterAsync", e);
             return StatsManager.PULL_SKIP;
         }
 
@@ -91,10 +103,11 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    private void initSubsystemSleepState() {
-        PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
-        if (entities == null) {
-            return;
+    private boolean initSubsystemSleepState() {
+        PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+        if (entities == null || entities.length == 0) {
+            Slog.e(TAG, "Failed to init SubsystemSleepState puller");
+            return false;
         }
 
         for (int i = 0; i < entities.length; i++) {
@@ -108,13 +121,20 @@
             mEntityNames.put(entity.id, entity.name);
             mStateNames.put(entity.id, states);
         }
+
+        return true;
     }
 
     private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
-        StateResidencyResult[] results =  mPowerStatsHALWrapper.getStateResidency(new int[0]);
-        if (results == null) {
+        final StateResidencyResult[] results;
+        try {
+            results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+                    .get(STATS_PULL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to getStateResidencyAsync", e);
             return StatsManager.PULL_SKIP;
         }
+
         for (int i = 0; i < results.length; i++) {
             final StateResidencyResult result = results[i];
             for (int j = 0; j < result.stateResidencyData.length; j++) {
@@ -131,22 +151,33 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+    public StatsPullAtomCallbackImpl(Context context, PowerStatsInternal powerStatsInternal) {
+        if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
         mContext = context;
-        mPowerStatsHALWrapper = powerStatsHALWrapper;
-        initPullOnDevicePowerMeasurement();
-        initSubsystemSleepState();
+        mPowerStatsInternal = powerStatsInternal;
+
+        if (powerStatsInternal == null) {
+            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+            return;
+        }
 
         StatsManager manager = mContext.getSystemService(StatsManager.class);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
-        manager.setPullAtomCallback(
-                FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
-                null, // use default PullAtomMetadata values
-                ConcurrentUtils.DIRECT_EXECUTOR,
-                this);
+
+        if (initPullOnDevicePowerMeasurement()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
+
+        if (initSubsystemSleepState()) {
+            manager.setPullAtomCallback(
+                    FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+                    null, // use default PullAtomMetadata values
+                    ConcurrentUtils.DIRECT_EXECUTOR,
+                    this);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS
index b94d988..31e3549 100644
--- a/services/core/java/com/android/server/role/OWNERS
+++ b/services/core/java/com/android/server/role/OWNERS
@@ -1,5 +1,4 @@
 svetoslavganov@google.com
-moltmann@google.com
 zhanghai@google.com
 evanseverson@google.com
 eugenesusla@google.com
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 57cf986..e57d4ce 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -66,7 +66,7 @@
     private static final String KEY_SERVICE_ENABLED = "service_enabled";
 
     /** Default value in absence of {@link DeviceConfig} override. */
-    private static final boolean DEFAULT_SERVICE_ENABLED = false;
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
 
     static final int ORIENTATION_UNKNOWN =
             FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 5e681c6..15c72b3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -90,6 +90,7 @@
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -138,6 +139,7 @@
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuBpfTracking;
 import com.android.internal.os.KernelCpuThreadReader;
 import com.android.internal.os.KernelCpuThreadReaderDiff;
 import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
@@ -152,6 +154,7 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.role.RoleManagerLocal;
@@ -457,6 +460,8 @@
                         synchronized (mCpuTimePerUidFreqLock) {
                             return pullCpuTimePerUidFreqLocked(atomTag, data);
                         }
+                    case FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER:
+                        return pullCpuCyclesPerThreadGroupCluster(atomTag, data);
                     case FrameworkStatsLog.CPU_ACTIVE_TIME:
                         synchronized (mCpuActiveTimeLock) {
                             return pullCpuActiveTimeLocked(atomTag, data);
@@ -781,6 +786,7 @@
         registerCpuTimePerUid();
         registerCpuCyclesPerUidCluster();
         registerCpuTimePerUidFreq();
+        registerCpuCyclesPerThreadGroupCluster();
         registerCpuActiveTime();
         registerCpuClusterTime();
         registerWifiActivityInfo();
@@ -1450,7 +1456,7 @@
     }
 
     private void registerCpuTimePerClusterFreq() {
-        if (KernelCpuTotalBpfMapReader.isSupported()) {
+        if (KernelCpuBpfTracking.isSupported()) {
             int tagId = FrameworkStatsLog.CPU_TIME_PER_CLUSTER_FREQ;
             PullAtomMetadata metadata = new PullAtomMetadata.Builder()
                     .setAdditiveFields(new int[] {3})
@@ -1510,6 +1516,7 @@
     }
 
     int pullCpuCyclesPerUidClusterLocked(int atomTag, List<StatsEvent> pulledData) {
+        // TODO(b/179485697): Remove power profile dependency.
         PowerProfile powerProfile = new PowerProfile(mContext);
         // Frequency index to frequency mapping.
         long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
@@ -1606,9 +1613,6 @@
         // Aggregate times for the same uids.
         SparseArray<long[]> aggregated = new SparseArray<>();
         mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
-            // For uids known to be aggregated from many entries allow mutating in place to avoid
-            // many copies. Otherwise, copy before aggregating.
-            boolean mutateInPlace = false;
             if (UserHandle.isIsolated(uid)) {
                 // Skip individual isolated uids because they are recycled and quickly removed from
                 // the underlying data source.
@@ -1616,26 +1620,18 @@
             } else if (UserHandle.isSharedAppGid(uid)) {
                 // All shared app gids are accounted together.
                 uid = LAST_SHARED_APPLICATION_GID;
-                mutateInPlace = true;
             } else {
                 // Everything else is accounted under their base uid.
                 uid = UserHandle.getAppId(uid);
             }
 
             long[] aggCpuFreqTimeMs = aggregated.get(uid);
-            if (aggCpuFreqTimeMs != null) {
-                if (!mutateInPlace) {
-                    aggCpuFreqTimeMs = Arrays.copyOf(aggCpuFreqTimeMs, cpuFreqTimeMs.length);
-                    aggregated.put(uid, aggCpuFreqTimeMs);
-                }
-                for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
-                    aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
-                }
-            } else {
-                if (mutateInPlace) {
-                    cpuFreqTimeMs = Arrays.copyOf(cpuFreqTimeMs, cpuFreqTimeMs.length);
-                }
-                aggregated.put(uid, cpuFreqTimeMs);
+            if (aggCpuFreqTimeMs == null) {
+                aggCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                aggregated.put(uid, aggCpuFreqTimeMs);
+            }
+            for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+                aggCpuFreqTimeMs[freqIndex] += cpuFreqTimeMs[freqIndex];
             }
         });
 
@@ -1653,6 +1649,82 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerCpuCyclesPerThreadGroupCluster() {
+        if (KernelCpuBpfTracking.isSupported()) {
+            int tagId = FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER;
+            PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+                    .setAdditiveFields(new int[] {3, 4})
+                    .build();
+            mStatsManager.setPullAtomCallback(
+                    tagId,
+                    metadata,
+                    DIRECT_EXECUTOR,
+                    mStatsCallbackImpl
+            );
+        }
+    }
+
+    int pullCpuCyclesPerThreadGroupCluster(int atomTag, List<StatsEvent> pulledData) {
+        // TODO(b/179485697): Remove power profile dependency.
+        PowerProfile powerProfile = new PowerProfile(mContext);
+        // Frequency index to frequency mapping.
+        long[] freqs = mCpuUidFreqTimeReader.readFreqs(powerProfile);
+        if (freqs == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        // Frequency index to cluster mapping.
+        int[] freqClusters = new int[freqs.length];
+        // Number of clusters.
+        int clusters;
+
+        // Initialize frequency mappings.
+        {
+            int cluster = 0;
+            long lastFreq = -1;
+            for (int freqIndex = 0; freqIndex < freqs.length; ++freqIndex) {
+                long currFreq = freqs[freqIndex];
+                if (currFreq <= lastFreq) {
+                    cluster++;
+                }
+                freqClusters[freqIndex] = cluster;
+                lastFreq = currFreq;
+            }
+
+            clusters = cluster + 1;
+        }
+
+        SystemServiceCpuThreadTimes times = LocalServices.getService(BatteryStatsInternal.class)
+                .getSystemServiceCpuThreadTimes();
+        if (times == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+                FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER,
+                times.threadCpuTimesUs, clusters, freqs, freqClusters);
+        addCpuCyclesPerThreadGroupClusterAtoms(atomTag, pulledData,
+                FrameworkStatsLog.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER__THREAD_GROUP__SYSTEM_SERVER_BINDER,
+                times.binderThreadCpuTimesUs, clusters, freqs, freqClusters);
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private static void addCpuCyclesPerThreadGroupClusterAtoms(
+            int atomTag, List<StatsEvent> pulledData, int threadGroup, long[] cpuTimesUs,
+            int clusters, long[] freqs, int[] freqClusters) {
+        long[] aggregatedCycles = new long[clusters];
+        long[] aggregatedTimesUs = new long[clusters];
+        for (int i = 0; i < cpuTimesUs.length; ++i) {
+            aggregatedCycles[freqClusters[i]] += freqs[i] * cpuTimesUs[i] / 1_000;
+            aggregatedTimesUs[freqClusters[i]] += cpuTimesUs[i];
+        }
+        for (int cluster = 0; cluster < clusters; ++cluster) {
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                    atomTag, threadGroup, cluster, aggregatedCycles[cluster] / 1_000_000L,
+                    aggregatedTimesUs[cluster] / 1_000));
+        }
+    }
+
     private void registerCpuActiveTime() {
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index eb4a050..0087c0c 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -158,6 +158,29 @@
     }
 
     /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long getAnrDelayMillis(String packageName, int uid)
+            throws ExternalStorageServiceException {
+        synchronized (mLock) {
+            int size = mConnections.size();
+            for (int i = 0; i < size; i++) {
+                int key = mConnections.keyAt(i);
+                StorageUserConnection connection = mConnections.get(key);
+                if (connection != null) {
+                    long delay = connection.getAnrDelayMillis(packageName, uid);
+                    if (delay > 0) {
+                        return delay;
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Removes and returns the {@link StorageUserConnection} for {@code vol}.
      *
      * Does nothing if {@link #shouldHandle} is {@code false}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 13cceee..709d558 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.server.storage;
 
+import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS;
 import static android.service.storage.ExternalStorageService.EXTRA_ERROR;
 import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE;
 import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE;
@@ -143,6 +144,24 @@
     }
 
     /**
+     * Called when {@code packageName} is about to ANR
+     *
+     * @return ANR dialog delay in milliseconds
+     */
+    public long getAnrDelayMillis(String packageName, int uid)
+            throws ExternalStorageServiceException {
+        synchronized (mSessionsLock) {
+            for (String sessionId : mSessions.keySet()) {
+                long delay = mActiveConnection.getAnrDelayMillis(packageName, uid);
+                if (delay > 0) {
+                    return delay;
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Removes a session without ending it or waiting for exit.
      *
      * This should only be used if the session has certainly been ended because the volume was
@@ -234,6 +253,9 @@
         @GuardedBy("mLock")
         private final ArrayList<CompletableFuture<Void>> mOutstandingOps = new ArrayList<>();
 
+        @GuardedBy("mLock")
+        private final ArrayList<CompletableFuture<Long>> mOutstandingTimeoutOps = new ArrayList<>();
+
         @Override
         public void close() {
             ServiceConnection oldConnection = null;
@@ -250,6 +272,9 @@
                 for (CompletableFuture<Void> op : mOutstandingOps) {
                     op.cancel(true);
                 }
+                for (CompletableFuture<Long> op : mOutstandingTimeoutOps) {
+                    op.cancel(true);
+                }
                 mOutstandingOps.clear();
             }
 
@@ -264,27 +289,44 @@
             }
         }
 
-        private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception {
-            CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
+        private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception {
             CompletableFuture<Void> opFuture = new CompletableFuture<>();
+            RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture));
+
+            waitForAsync(asyncCall, callback, opFuture, mOutstandingOps,
+                    DEFAULT_REMOTE_TIMEOUT_SECONDS);
+        }
+
+        private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception {
+            CompletableFuture<Long> opFuture = new CompletableFuture<>();
+            RemoteCallback callback =
+                    new RemoteCallback(result -> setTimeoutResult(result, opFuture));
+
+            return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps,
+                    1 /* timeoutSeconds */);
+        }
+
+        private <T> T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback,
+                CompletableFuture<T> opFuture, ArrayList<CompletableFuture<T>> outstandingOps,
+                long timeoutSeconds) throws Exception {
+            CompletableFuture<IExternalStorageService> serviceFuture = connectIfNeeded();
 
             try {
                 synchronized (mLock) {
-                    mOutstandingOps.add(opFuture);
+                    outstandingOps.add(opFuture);
                 }
-                serviceFuture.thenCompose(service -> {
+                return serviceFuture.thenCompose(service -> {
                     try {
-                        asyncCall.run(service,
-                                new RemoteCallback(result -> setResult(result, opFuture)));
+                        asyncCall.run(service, callback);
                     } catch (RemoteException e) {
                         opFuture.completeExceptionally(e);
                     }
 
                     return opFuture;
-                }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+                }).get(timeoutSeconds, TimeUnit.SECONDS);
             } finally {
                 synchronized (mLock) {
-                    mOutstandingOps.remove(opFuture);
+                    outstandingOps.remove(opFuture);
                 }
             }
         }
@@ -292,9 +334,9 @@
         public void startSession(Session session, ParcelFileDescriptor fd)
                 throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) -> service.startSession(session.sessionId,
+                waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId,
                         FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
-                        fd, session.upperPath, session.lowerPath, callback));
+                                fd, session.upperPath, session.lowerPath, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to start session: " + session, e);
             } finally {
@@ -308,7 +350,7 @@
 
         public void endSession(Session session) throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.endSession(session.sessionId, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to end session: " + session, e);
@@ -319,7 +361,7 @@
         public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws
                 ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.notifyVolumeStateChanged(sessionId, vol, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to notify volume state changed "
@@ -330,7 +372,7 @@
         public void freeCache(String sessionId, String volumeUuid, long bytes)
                 throws ExternalStorageServiceException {
             try {
-                waitForAsync((service, callback) ->
+                waitForAsyncVoid((service, callback) ->
                         service.freeCache(sessionId, volumeUuid, bytes, callback));
             } catch (Exception e) {
                 throw new ExternalStorageServiceException("Failed to free " + bytes
@@ -338,6 +380,27 @@
             }
         }
 
+        public long getAnrDelayMillis(String packgeName, int uid)
+                throws ExternalStorageServiceException {
+            try {
+                return waitForAsyncLong((service, callback) ->
+                        service.getAnrDelayMillis(packgeName, uid, callback));
+            } catch (Exception e) {
+                throw new ExternalStorageServiceException("Failed to notify app not responding: "
+                        + packgeName, e);
+            }
+        }
+
+        private void setTimeoutResult(Bundle result, CompletableFuture<Long> future) {
+            ParcelableException ex = result.getParcelable(EXTRA_ERROR);
+            if (ex != null) {
+                future.completeExceptionally(ex);
+            } else {
+                long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS);
+                future.complete(timeoutMs);
+            }
+        }
+
         private void setResult(Bundle result, CompletableFuture<Void> future) {
             ParcelableException ex = result.getParcelable(EXTRA_ERROR);
             if (ex != null) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index bbbd19f..b210339 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ITimeDetectorService;
 import android.app.timedetector.ManualTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 6a4c276..792f372 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 7cd4184..c4c620c 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,7 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
index a54288f..e463ee2 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -33,9 +33,11 @@
  */
 class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
 
-    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+    // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
+    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1);
+    // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented
     private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
-            Duration.ofMinutes(1);
+            Duration.ofSeconds(20);
     private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
 
     @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2503e81..0ed26db 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
@@ -37,10 +38,12 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgent.ValidationStatus;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
 import android.net.annotations.PolicyDirection;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -58,6 +61,9 @@
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -65,6 +71,7 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
@@ -120,15 +127,37 @@
  * +----------------------------+
  * </pre>
  *
+ * <p>All messages in VcnGatewayConnection <b>should</b> be enqueued using {@link
+ * #sendMessageAndAcquireWakeLock}. Careful consideration should be given to any uses of {@link
+ * #sendMessage} directly, as they are not guaranteed to be processed in a timely manner (due to the
+ * lack of WakeLocks).
+ *
+ * <p>Any attempt to remove messages from the Handler should be done using {@link
+ * #removeEqualMessages}. This is necessary to ensure that the WakeLock is correctly released when
+ * no messages remain in the Handler queue.
+ *
  * @hide
  */
 public class VcnGatewayConnection extends StateMachine {
     private static final String TAG = VcnGatewayConnection.class.getSimpleName();
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String TEARDOWN_TIMEOUT_ALARM = TAG + "_TEARDOWN_TIMEOUT_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String DISCONNECT_REQUEST_ALARM = TAG + "_DISCONNECT_REQUEST_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";
+
     private static final int[] MERGED_CAPABILITIES =
             new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
-
-    private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
     private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
 
     private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
@@ -137,11 +166,15 @@
     private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
-    private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int TEARDOWN_TIMEOUT_SECONDS = 5;
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int SAFEMODE_TIMEOUT_SECONDS = 30;
+
     private interface EventInfo {}
 
     /**
@@ -384,6 +417,23 @@
     // TODO(b/178426520): implement handling of this event
     private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
 
+    /**
+     * Sent when this VcnGatewayConnection has entered Safemode.
+     *
+     * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
+     * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
+     *
+     * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
+     * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
+     * its VcnGatewayConnectin(s).
+     *
+     * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
+     * validated yet), and RetryTimeoutState.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     */
+    private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
+
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     @NonNull
     final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -412,11 +462,26 @@
     @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
     @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull private final Dependencies mDeps;
-
     @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
 
     @NonNull private final IpSecManager mIpSecManager;
-    @NonNull private final IpSecTunnelInterface mTunnelIface;
+
+    @Nullable private IpSecTunnelInterface mTunnelIface = null;
+
+    /**
+     * WakeLock to be held when processing messages on the Handler queue.
+     *
+     * <p>Used to prevent the device from going to sleep while there are VCN-related events to
+     * process for this VcnGatewayConnection.
+     *
+     * <p>Obtain a WakeLock when enquing messages onto the Handler queue. Once all messages in the
+     * Handler queue have been processed, the WakeLock can be released and cleared.
+     *
+     * <p>This WakeLock is also used for handling delayed messages by using WakeupMessages to send
+     * delayed messages to the Handler. When the WakeupMessage fires, it will obtain the WakeLock
+     * before enquing the delayed event to the Handler.
+     */
+    @NonNull private final VcnWakeLock mWakeLock;
 
     /** Running state of this VcnGatewayConnection. */
     private boolean mIsRunning = true;
@@ -480,7 +545,13 @@
      * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
      * otherwise.
      */
-    private NetworkAgent mNetworkAgent;
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    NetworkAgent mNetworkAgent;
+
+    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
+    @Nullable private WakeupMessage mDisconnectRequestAlarm;
+    @Nullable private WakeupMessage mRetryTimeoutAlarm;
+    @Nullable private WakeupMessage mSafemodeTimeoutAlarm;
 
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
@@ -517,6 +588,9 @@
 
         mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
 
+        mWakeLock =
+                mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
                         mVcnContext,
@@ -526,20 +600,6 @@
                         mUnderlyingNetworkTrackerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
 
-        IpSecTunnelInterface iface;
-        try {
-            iface =
-                    mIpSecManager.createIpSecTunnelInterface(
-                            DUMMY_ADDR, DUMMY_ADDR, new Network(-1));
-        } catch (IOException | ResourceUnavailableException e) {
-            teardownAsynchronously();
-            mTunnelIface = null;
-
-            return;
-        }
-
-        mTunnelIface = iface;
-
         addState(mDisconnectedState);
         addState(mDisconnectingState);
         addState(mConnectingState);
@@ -557,7 +617,7 @@
      * <p>Once torn down, this VcnTunnel CANNOT be started again.
      */
     public void teardownAsynchronously() {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_DISCONNECT_REQUESTED,
                 TOKEN_ALL,
                 new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
@@ -573,6 +633,13 @@
             mTunnelIface.close();
         }
 
+        releaseWakeLock();
+
+        cancelTeardownTimeoutAlarm();
+        cancelDisconnectRequestAlarm();
+        cancelRetryTimeoutAlarm();
+        cancelSafemodeAlarm();
+
         mUnderlyingNetworkTracker.teardown();
     }
 
@@ -589,79 +656,324 @@
         mLastSnapshot = snapshot;
         mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
 
-        sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
     }
 
     private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
         @Override
         public void onSelectedUnderlyingNetworkChanged(
                 @Nullable UnderlyingNetworkRecord underlying) {
+            // TODO(b/180132994): explore safely removing this Thread check
+            mVcnContext.ensureRunningOnLooperThread();
+
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
             // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
             // timeout.
             if (underlying == null) {
-                sendMessageDelayed(
-                        EVENT_DISCONNECT_REQUESTED,
-                        TOKEN_ALL,
-                        new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST),
-                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
-            } else if (getHandler() != null) {
-                // Cancel any existing disconnect due to loss of underlying network
-                // getHandler() can return null if the state machine has already quit. Since this is
-                // called from other classes, this condition must be verified.
-
-                getHandler()
-                        .removeEqualMessages(
-                                EVENT_DISCONNECT_REQUESTED,
-                                new EventDisconnectRequestedInfo(
-                                        DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+                setDisconnectRequestAlarm();
+            } else {
+                // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
+                // and cancel any queued EVENT_DISCONNECT_REQUEST messages
+                cancelDisconnectRequestAlarm();
             }
 
-            sendMessage(
+            sendMessageAndAcquireWakeLock(
                     EVENT_UNDERLYING_NETWORK_CHANGED,
                     TOKEN_ALL,
                     new EventUnderlyingNetworkChangedInfo(underlying));
         }
     }
 
-    private void sendMessage(int what, int token, EventInfo data) {
+    private void acquireWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (mIsRunning) {
+            mWakeLock.acquire();
+        }
+    }
+
+    private void releaseWakeLock() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mWakeLock.release();
+    }
+
+    /**
+     * Attempt to release mWakeLock - this can only be done if the Handler is null (meaning the
+     * StateMachine has been shutdown and thus has no business keeping the WakeLock) or if there are
+     * no more messags left to process in the Handler queue (at which point the WakeLock can be
+     * released until more messages must be processed).
+     */
+    private void maybeReleaseWakeLock() {
+        final Handler handler = getHandler();
+        if (handler == null || !handler.hasMessagesOrCallbacks()) {
+            releaseWakeLock();
+        }
+    }
+
+    @Override
+    public void sendMessage(int what) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what);
+    }
+
+    @Override
+    public void sendMessage(int what, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, obj);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2);
+    }
+
+    @Override
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(what, arg1, arg2, obj);
+    }
+
+    @Override
+    public void sendMessage(Message msg) {
+        Slog.wtf(
+                TAG,
+                "sendMessage should not be used in VcnGatewayConnection. See"
+                        + " sendMessageAndAcquireWakeLock()");
+        super.sendMessage(msg);
+    }
+
+    // TODO(b/180146061): also override and Log.wtf() other Message handling methods
+    // In mind are sendMessageDelayed(), sendMessageAtFrontOfQueue, removeMessages, and
+    // removeDeferredMessages
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token) {
+        acquireWakeLock();
+        super.sendMessage(what, token);
+    }
+
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, ARG_NOT_PRESENT, data);
     }
 
-    private void sendMessage(int what, int token, int arg2, EventInfo data) {
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(int what, int token, int arg2, EventInfo data) {
+        acquireWakeLock();
         super.sendMessage(what, token, arg2, data);
     }
 
-    private void sendMessageDelayed(int what, int token, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, ARG_NOT_PRESENT, data, timeout);
+    /**
+     * WakeLock-based alternative to {@link #sendMessage}. Use to guarantee that the device will not
+     * go to sleep before processing the sent message.
+     */
+    private void sendMessageAndAcquireWakeLock(Message msg) {
+        acquireWakeLock();
+        super.sendMessage(msg);
     }
 
-    private void sendMessageDelayed(int what, int token, int arg2, EventInfo data, long timeout) {
-        super.sendMessageDelayed(what, token, arg2, data, timeout);
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     */
+    private void removeEqualMessages(int what) {
+        removeEqualMessages(what, null /* obj */);
+    }
+
+    /**
+     * Removes all messages matching the given parameters, and attempts to release mWakeLock if the
+     * Handler is empty.
+     *
+     * @param what the Message.what value to be removed
+     * @param obj the Message.obj to to be removed, or null if all messages matching Message.what
+     *     should be removed
+     */
+    private void removeEqualMessages(int what, @Nullable Object obj) {
+        final Handler handler = getHandler();
+        if (handler != null) {
+            handler.removeEqualMessages(what, obj);
+        }
+
+        maybeReleaseWakeLock();
+    }
+
+    private WakeupMessage createScheduledAlarm(
+            @NonNull String cmdName, Message delayedMessage, long delay) {
+        // WakeupMessage uses Handler#dispatchMessage() to immediately handle the specified Runnable
+        // at the scheduled time. dispatchMessage() immediately executes and there may be queued
+        // events that resolve the scheduled alarm pending in the queue. So, use the Runnable to
+        // place the alarm event at the end of the queue with sendMessageAndAcquireWakeLock (which
+        // guarantees the device will stay awake).
+        final WakeupMessage alarm =
+                mDeps.newWakeupMessage(
+                        mVcnContext,
+                        getHandler(),
+                        cmdName,
+                        () -> sendMessageAndAcquireWakeLock(delayedMessage));
+        alarm.schedule(mDeps.getElapsedRealTime() + delay);
+        return alarm;
+    }
+
+    private void setTeardownTimeoutAlarm() {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mTeardownTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mTeardownTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_TEARDOWN_TIMEOUT_EXPIRED, mCurrentToken);
+        mTeardownTimeoutAlarm =
+                createScheduledAlarm(
+                        TEARDOWN_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+    }
+
+    private void cancelTeardownTimeoutAlarm() {
+        if (mTeardownTimeoutAlarm != null) {
+            mTeardownTimeoutAlarm.cancel();
+            mTeardownTimeoutAlarm = null;
+        }
+
+        // Cancel any existing teardown timeouts
+        removeEqualMessages(EVENT_TEARDOWN_TIMEOUT_EXPIRED);
+    }
+
+    private void setDisconnectRequestAlarm() {
+        // Only schedule a NEW alarm if none is already set.
+        if (mDisconnectRequestAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage =
+                obtainMessage(
+                        EVENT_DISCONNECT_REQUESTED,
+                        TOKEN_ALL,
+                        0 /* arg2 */,
+                        new EventDisconnectRequestedInfo(
+                                DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+        mDisconnectRequestAlarm =
+                createScheduledAlarm(
+                        DISCONNECT_REQUEST_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS));
+    }
+
+    private void cancelDisconnectRequestAlarm() {
+        if (mDisconnectRequestAlarm != null) {
+            mDisconnectRequestAlarm.cancel();
+            mDisconnectRequestAlarm = null;
+        }
+
+        // Cancel any existing disconnect due to previous loss of underlying network
+        removeEqualMessages(
+                EVENT_DISCONNECT_REQUESTED,
+                new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
+    }
+
+    private void setRetryTimeoutAlarm(long delay) {
+        // Safe to assign this alarm because it is either 1) already null, or 2) already fired. In
+        // either case, there is nothing to cancel.
+        if (mRetryTimeoutAlarm != null) {
+            Slog.wtf(TAG, "mRetryTimeoutAlarm should be null before being set");
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken);
+        mRetryTimeoutAlarm = createScheduledAlarm(RETRY_TIMEOUT_ALARM, delayedMessage, delay);
+    }
+
+    private void cancelRetryTimeoutAlarm() {
+        if (mRetryTimeoutAlarm != null) {
+            mRetryTimeoutAlarm.cancel();
+            mRetryTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setSafemodeAlarm() {
+        // Only schedule a NEW alarm if none is already set.
+        if (mSafemodeTimeoutAlarm != null) {
+            return;
+        }
+
+        final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
+        mSafemodeTimeoutAlarm =
+                createScheduledAlarm(
+                        SAFEMODE_TIMEOUT_ALARM,
+                        delayedMessage,
+                        TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    private void cancelSafemodeAlarm() {
+        if (mSafemodeTimeoutAlarm != null) {
+            mSafemodeTimeoutAlarm.cancel();
+            mSafemodeTimeoutAlarm = null;
+        }
+
+        removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
     }
 
     private void sessionLost(int token, @Nullable Exception exception) {
-        sendMessage(EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
     }
 
     private void sessionClosed(int token, @Nullable Exception exception) {
         // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
         // Disconnecting state.
         sessionLost(token, exception);
-        sendMessage(EVENT_SESSION_CLOSED, token);
+        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
     }
 
     private void childTransformCreated(
             int token, @NonNull IpSecTransform transform, int direction) {
-        sendMessage(
+        sendMessageAndAcquireWakeLock(
                 EVENT_TRANSFORM_CREATED,
                 token,
                 new EventTransformCreatedInfo(direction, transform));
     }
 
     private void childOpened(int token, @NonNull VcnChildSessionConfiguration childConfig) {
-        sendMessage(EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
+        sendMessageAndAcquireWakeLock(
+                EVENT_SETUP_COMPLETED, token, new EventSetupCompletedInfo(childConfig));
     }
 
     private abstract class BaseState extends State {
@@ -671,7 +983,7 @@
                 enterState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -682,22 +994,47 @@
         protected void enterState() throws Exception {}
 
         /**
+         * Returns whether the given token is valid.
+         *
+         * <p>By default, States consider any and all token to be 'valid'.
+         *
+         * <p>States should override this method if they want to restrict message handling to
+         * specific tokens.
+         */
+        protected boolean isValidToken(int token) {
+            return true;
+        }
+
+        /**
          * Top-level processMessage with safeguards to prevent crashing the System Server on non-eng
          * builds.
+         *
+         * <p>Here be dragons: processMessage() is final to ensure that mWakeLock is released once
+         * the Handler queue is empty. Future changes (or overrides) to processMessage() to MUST
+         * ensure that mWakeLock is correctly released.
          */
         @Override
-        public boolean processMessage(Message msg) {
+        public final boolean processMessage(Message msg) {
+            final int token = msg.arg1;
+            if (!isValidToken(token)) {
+                Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
+                return HANDLED;
+            }
+
             try {
                 processStateMsg(msg);
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
                                 DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
             }
 
+            // Attempt to release the WakeLock - only possible if the Handler queue is empty
+            maybeReleaseWakeLock();
+
             return HANDLED;
         }
 
@@ -709,7 +1046,7 @@
                 exitState();
             } catch (Exception e) {
                 Slog.wtf(TAG, "Uncaught exception", e);
-                sendMessage(
+                sendMessageAndAcquireWakeLock(
                         EVENT_DISCONNECT_REQUESTED,
                         TOKEN_ALL,
                         new EventDisconnectRequestedInfo(
@@ -787,6 +1124,8 @@
             if (mIkeSession != null || mNetworkAgent != null) {
                 Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
             }
+
+            cancelSafemodeAlarm();
         }
 
         @Override
@@ -810,27 +1149,16 @@
                     break;
             }
         }
+
+        @Override
+        protected void exitState() {
+            // Safe to blindly set up, as it is cancelled and cleared on entering this state
+            setSafemodeAlarm();
+        }
     }
 
     private abstract class ActiveBaseState extends BaseState {
-        /**
-         * Handles all incoming messages, discarding messages for previous networks.
-         *
-         * <p>States that handle mobility events may need to override this method to receive
-         * messages for all underlying networks.
-         */
         @Override
-        public boolean processMessage(Message msg) {
-            final int token = msg.arg1;
-            // Only process if a valid token is presented.
-            if (isValidToken(token)) {
-                return super.processMessage(msg);
-            }
-
-            Slog.v(TAG, "Message called with obsolete token: " + token + "; what: " + msg.what);
-            return HANDLED;
-        }
-
         protected boolean isValidToken(int token) {
             return (token == TOKEN_ALL || token == mCurrentToken);
         }
@@ -861,7 +1189,7 @@
         protected void enterState() throws Exception {
             if (mIkeSession == null) {
                 Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
-                sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+                sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, mCurrentToken);
                 return;
             }
 
@@ -873,10 +1201,9 @@
             }
 
             mIkeSession.close();
-            sendMessageDelayed(
-                    EVENT_TEARDOWN_TIMEOUT_EXPIRED,
-                    mCurrentToken,
-                    TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+
+            // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+            setTeardownTimeoutAlarm();
         }
 
         @Override
@@ -918,6 +1245,10 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -927,6 +1258,8 @@
         @Override
         protected void exitState() throws Exception {
             mSkipRetryTimeout = false;
+
+            cancelTeardownTimeoutAlarm();
         }
     }
 
@@ -998,6 +1331,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1041,6 +1378,14 @@
                         public void unwanted() {
                             teardownAsynchronously();
                         }
+
+                        @Override
+                        public void onValidationStatus(
+                                @ValidationStatus int status, @Nullable Uri redirectUri) {
+                            if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+                                clearFailedAttemptCounterAndSafeModeAlarm();
+                            }
+                        }
                     };
 
             agent.register();
@@ -1049,6 +1394,14 @@
             return agent;
         }
 
+        protected void clearFailedAttemptCounterAndSafeModeAlarm() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            // Validated connection, clear failed attempt counter
+            mFailedAttempts = 0;
+            cancelSafemodeAlarm();
+        }
+
         protected void applyTransform(
                 int token,
                 @NonNull IpSecTunnelInterface tunnelIface,
@@ -1117,8 +1470,17 @@
     class ConnectedState extends ConnectedStateBase {
         @Override
         protected void enterState() throws Exception {
-            // Successful connection, clear failed attempt counter
-            mFailedAttempts = 0;
+            if (mTunnelIface == null) {
+                try {
+                    // Requires a real Network object in order to be created; doing this any earlier
+                    // means not having a real Network object, or picking an incorrect Network.
+                    mTunnelIface =
+                            mIpSecManager.createIpSecTunnelInterface(
+                                    DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network);
+                } catch (IOException | ResourceUnavailableException e) {
+                    teardownAsynchronously();
+                }
+            }
         }
 
         @Override
@@ -1155,6 +1517,10 @@
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
@@ -1197,8 +1563,19 @@
                 mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
             } else {
                 updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
+
+                // mNetworkAgent not null, so the VCN Network has already been established. Clear
+                // the failed attempt counter and safe mode alarm since this transition is complete.
+                clearFailedAttemptCounterAndSafeModeAlarm();
             }
         }
+
+        @Override
+        protected void exitState() {
+            // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
+            // while in ConnectedState (which cancels the previous alarm)
+            setSafemodeAlarm();
+        }
     }
 
     /**
@@ -1216,8 +1593,8 @@
                 Slog.wtf(TAG, "Underlying network was null in retry state");
                 transitionTo(mDisconnectedState);
             } else {
-                sendMessageDelayed(
-                        EVENT_RETRY_TIMEOUT_EXPIRED, mCurrentToken, getNextRetryIntervalsMs());
+                // Safe to blindly set up, as it is cancelled and cleared on exiting this state
+                setRetryTimeoutAlarm(getNextRetryIntervalsMs());
             }
         }
 
@@ -1230,8 +1607,6 @@
 
                     // If new underlying is null, all networks were lost; go back to disconnected.
                     if (mUnderlying == null) {
-                        removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
                         transitionTo(mDisconnectedState);
                         return;
                     } else if (oldUnderlying != null
@@ -1242,19 +1617,26 @@
 
                     // Fallthrough
                 case EVENT_RETRY_TIMEOUT_EXPIRED:
-                    removeMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
-
                     transitionTo(mConnectingState);
                     break;
                 case EVENT_DISCONNECT_REQUESTED:
                     handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                     break;
+                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
+                    mGatewayStatusCallback.onEnteredSafemode();
+                    mSafemodeTimeoutAlarm = null;
+                    break;
                 default:
                     logUnhandledMessage(msg);
                     break;
             }
         }
 
+        @Override
+        public void exitState() {
+            cancelRetryTimeoutAlarm();
+        }
+
         private long getNextRetryIntervalsMs() {
             final int retryDelayIndex = mFailedAttempts - 1;
             final long[] retryIntervalsMs = mConnectionConfig.getRetryIntervalsMs();
@@ -1434,6 +1816,11 @@
     }
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setTunnelInterface(IpSecTunnelInterface tunnelIface) {
+        mTunnelIface = tunnelIface;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
     UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
         return mUnderlyingNetworkTrackerCallback;
     }
@@ -1522,6 +1909,26 @@
                     ikeSessionCallback,
                     childSessionCallback);
         }
+
+        /** Builds a new WakeLock. */
+        public VcnWakeLock newWakeLock(
+                @NonNull Context context, int wakeLockFlag, @NonNull String wakeLockTag) {
+            return new VcnWakeLock(context, wakeLockFlag, wakeLockTag);
+        }
+
+        /** Builds a new WakeupMessage. */
+        public WakeupMessage newWakeupMessage(
+                @NonNull VcnContext vcnContext,
+                @NonNull Handler handler,
+                @NonNull String tag,
+                @NonNull Runnable runnable) {
+            return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
+        }
+
+        /** Gets the elapsed real time since boot, in millis. */
+        public long getElapsedRealTime() {
+            return SystemClock.elapsedRealtime();
+        }
     }
 
     /**
@@ -1601,4 +2008,34 @@
             mImpl.setNetwork(network);
         }
     }
+
+    /** Proxy Implementation of WakeLock, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnWakeLock {
+        private final WakeLock mImpl;
+
+        public VcnWakeLock(@NonNull Context context, int flags, @NonNull String tag) {
+            final PowerManager powerManager = context.getSystemService(PowerManager.class);
+            mImpl = powerManager.newWakeLock(flags, tag);
+            mImpl.setReferenceCounted(false /* isReferenceCounted */);
+        }
+
+        /**
+         * Acquire this WakeLock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void acquire() {
+            mImpl.acquire();
+        }
+
+        /**
+         * Release this Wakelock.
+         *
+         * <p>Synchronize this action to minimize locking around WakeLock use.
+         */
+        public synchronized void release() {
+            mImpl.release();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 3968723..685dce4 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -21,16 +21,14 @@
 import android.os.CombinedVibrationEffect;
 import android.os.Handler;
 import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorManager;
 import android.util.SparseArray;
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
 
-/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
+/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
+final class InputDeviceDelegate implements InputManager.InputDeviceListener {
     private static final String TAG = "InputDeviceDelegate";
 
     private final Object mLock = new Object();
@@ -38,7 +36,7 @@
     private final InputManager mInputManager;
 
     @GuardedBy("mLock")
-    private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();
+    private final SparseArray<VibratorManager> mInputDeviceVibrators = new SparseArray<>();
 
     /**
      * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
@@ -47,7 +45,7 @@
     @GuardedBy("mLock")
     private boolean mShouldVibrateInputDevices;
 
-    public InputDeviceDelegate(Context context, Handler handler) {
+    InputDeviceDelegate(Context context, Handler handler) {
         mHandler = handler;
         mInputManager = context.getSystemService(InputManager.class);
     }
@@ -81,32 +79,22 @@
     }
 
     /**
-     * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
+     * Vibrate all {@link InputDevice} with vibrators using given effect.
      *
      * @return {@link #isAvailable()}
      */
     public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibrationEffect effect,
             String reason, VibrationAttributes attrs) {
         synchronized (mLock) {
-            // TODO(b/159207608): Pass on the combined vibration once InputManager is merged
-            if (effect instanceof CombinedVibrationEffect.Mono) {
-                VibrationEffect e = ((CombinedVibrationEffect.Mono) effect).getEffect();
-                if (e instanceof VibrationEffect.Prebaked) {
-                    VibrationEffect fallback = ((VibrationEffect.Prebaked) e).getFallbackEffect();
-                    if (fallback != null) {
-                        e = fallback;
-                    }
-                }
-                for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
-                    mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, e, reason, attrs);
-                }
+            for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
+                mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
             }
             return mInputDeviceVibrators.size() > 0;
         }
     }
 
     /**
-     * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
+     * Cancel vibration on all {@link InputDevice} with vibrators.
      *
      * @return {@link #isAvailable()}
      */
@@ -147,9 +135,9 @@
                     if (device == null) {
                         continue;
                     }
-                    Vibrator vibrator = device.getVibrator();
-                    if (vibrator.hasVibrator()) {
-                        mInputDeviceVibrators.put(device.getId(), vibrator);
+                    VibratorManager vibratorManager = device.getVibratorManager();
+                    if (vibratorManager.getVibratorIds().length > 0) {
+                        mInputDeviceVibrators.put(device.getId(), vibratorManager);
                     }
                 }
             } else {
@@ -171,9 +159,9 @@
                 mInputDeviceVibrators.remove(deviceId);
                 return;
             }
-            Vibrator vibrator = device.getVibrator();
-            if (vibrator.hasVibrator()) {
-                mInputDeviceVibrators.put(deviceId, vibrator);
+            VibratorManager vibratorManager = device.getVibratorManager();
+            if (vibratorManager.getVibratorIds().length > 0) {
+                mInputDeviceVibrators.put(device.getId(), vibratorManager);
             } else {
                 mInputDeviceVibrators.remove(deviceId);
             }
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e0f5408..607da3c 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,25 +25,16 @@
 import android.os.VibrationEffect;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.ComposedProto;
-import com.android.server.OneShotProto;
-import com.android.server.PrebakedProto;
-import com.android.server.VibrationAttributesProto;
-import com.android.server.VibrationEffectProto;
-import com.android.server.VibrationProto;
-import com.android.server.WaveformProto;
-
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 /** Represents a vibration request to the vibrator service. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public class Vibration {
+final class Vibration {
     private static final String TAG = "Vibration";
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
-    public enum Status {
+    enum Status {
         RUNNING,
         FINISHED,
         FORWARDED_TO_INPUT_DEVICES,
@@ -91,7 +82,7 @@
     private long mEndTimeDebug;
     private Status mStatus;
 
-    public Vibration(IBinder token, int id, CombinedVibrationEffect effect,
+    Vibration(IBinder token, int id, CombinedVibrationEffect effect,
             VibrationAttributes attrs, int uid, String opPkg, String reason) {
         this.token = token;
         this.mEffect = effect;
@@ -157,7 +148,7 @@
     }
 
     /** Debug information about vibrations. */
-    public static final class DebugInfo {
+    static final class DebugInfo {
         private final long mStartTimeDebug;
         private final long mEndTimeDebug;
         private final CombinedVibrationEffect mEffect;
@@ -169,7 +160,7 @@
         private final String mReason;
         private final Status mStatus;
 
-        public DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
+        DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibrationEffect effect,
                 CombinedVibrationEffect originalEffect, float scale, VibrationAttributes attrs,
                 int uid, String opPkg, String reason, Status status) {
             mStartTimeDebug = startTimeDebug;
@@ -235,21 +226,49 @@
         }
 
         private void dumpEffect(
-                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect combinedEffect) {
-            VibrationEffect effect;
-            // TODO(b/177805090): add proper support for dumping combined effects to proto
-            if (combinedEffect instanceof CombinedVibrationEffect.Mono) {
-                effect = ((CombinedVibrationEffect.Mono) combinedEffect).getEffect();
-            } else if (combinedEffect instanceof CombinedVibrationEffect.Stereo) {
-                effect = ((CombinedVibrationEffect.Stereo) combinedEffect).getEffects().valueAt(0);
-            } else if (combinedEffect instanceof CombinedVibrationEffect.Sequential) {
-                dumpEffect(proto, fieldId,
-                        ((CombinedVibrationEffect.Sequential) combinedEffect).getEffects().get(0));
-                return;
-            } else {
-                // Unknown combined effect, skip dump.
-                return;
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect effect) {
+            dumpEffect(proto, fieldId,
+                    (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+                            .addNext(effect)
+                            .combine());
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Sequential effect) {
+            final long token = proto.start(fieldId);
+            for (int i = 0; i < effect.getEffects().size(); i++) {
+                CombinedVibrationEffect nestedEffect = effect.getEffects().get(i);
+                if (nestedEffect instanceof CombinedVibrationEffect.Mono) {
+                    dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+                            (CombinedVibrationEffect.Mono) nestedEffect);
+                } else if (nestedEffect instanceof CombinedVibrationEffect.Stereo) {
+                    dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS,
+                            (CombinedVibrationEffect.Stereo) nestedEffect);
+                }
+                proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i));
             }
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Mono effect) {
+            final long token = proto.start(fieldId);
+            dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect());
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, CombinedVibrationEffect.Stereo effect) {
+            final long token = proto.start(fieldId);
+            for (int i = 0; i < effect.getEffects().size(); i++) {
+                proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i));
+                dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i));
+            }
+            proto.end(token);
+        }
+
+        private void dumpEffect(
+                ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
             final long token = proto.start(fieldId);
             if (effect instanceof VibrationEffect.OneShot) {
                 dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 0fa4fe1..10393f6 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -29,8 +29,7 @@
 import java.util.Objects;
 
 /** Controls vibration scaling. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationScaler {
+final class VibrationScaler {
     private static final String TAG = "VibrationScaler";
 
     // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
@@ -56,7 +55,7 @@
     private final VibrationSettings mSettingsController;
     private final int mDefaultVibrationAmplitude;
 
-    public VibrationScaler(Context context, VibrationSettings settingsController) {
+    VibrationScaler(Context context, VibrationSettings settingsController) {
         mSettingsController = settingsController;
         mDefaultVibrationAmplitude = context.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultVibrationAmplitude);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8910bdf..334129d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -39,20 +39,18 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
-import com.android.server.VibratorServiceDumpProto;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Controls all the system settings related to vibration. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationSettings {
+final class VibrationSettings {
     private static final String TAG = "VibrationSettings";
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = {0, 30, 100, 30};
 
     /** Listener for changes on vibration settings. */
-    public interface OnVibratorSettingsChanged {
+    interface OnVibratorSettingsChanged {
         /** Callback triggered when any of the vibrator settings change. */
         void onChange();
     }
@@ -86,7 +84,7 @@
     @GuardedBy("mLock")
     private boolean mLowPowerMode;
 
-    public VibrationSettings(Context context, Handler handler) {
+    VibrationSettings(Context context, Handler handler) {
         mContext = context;
         mVibrator = context.getSystemService(Vibrator.class);
         mAudioManager = context.getSystemService(AudioManager.class);
@@ -345,17 +343,17 @@
     /** Write current settings into given {@link ProtoOutputStream}. */
     public void dumpProto(ProtoOutputStream proto) {
         synchronized (mLock) {
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
                     mHapticFeedbackIntensity);
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
                     mVibrator.getDefaultHapticFeedbackIntensity());
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
                     mNotificationIntensity);
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
                     mVibrator.getDefaultNotificationVibrationIntensity());
-            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
                     mRingIntensity);
-            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+            proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
                     mVibrator.getDefaultRingVibrationIntensity());
         }
     }
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index bee66637..3893267 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -42,8 +42,7 @@
 import java.util.PriorityQueue;
 
 /** Plays a {@link Vibration} in dedicated thread. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibrationThread extends Thread implements IBinder.DeathRecipient {
+final class VibrationThread extends Thread implements IBinder.DeathRecipient {
     private static final String TAG = "VibrationThread";
     private static final boolean DEBUG = false;
 
@@ -54,7 +53,7 @@
     private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
 
     /** Callbacks for playing a {@link Vibration}. */
-    public interface VibrationCallbacks {
+    interface VibrationCallbacks {
 
         /**
          * Callback triggered before starting a synchronized vibration step. This will be called
@@ -92,14 +91,7 @@
     @GuardedBy("mLock")
     private boolean mForceStop;
 
-    // TODO(b/159207608): Remove this constructor once VibratorService is removed
-    public VibrationThread(Vibration vib, VibratorController vibrator,
-            PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
-            VibrationCallbacks callbacks) {
-        this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks);
-    }
-
-    public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
+    VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
             VibrationCallbacks callbacks) {
         mVibration = vib;
@@ -176,7 +168,7 @@
         }
     }
 
-    public Vibration getVibration() {
+    Vibration getVibration() {
         return mVibration;
     }
 
@@ -286,12 +278,6 @@
         return filteredEffects;
     }
 
-    private static SparseArray<VibratorController> toSparseArray(VibratorController controller) {
-        SparseArray<VibratorController> array = new SparseArray<>(1);
-        array.put(controller.getVibratorInfo().getId(), controller);
-        return array;
-    }
-
     /**
      * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code
      * startIndex} until the next time it's vibrating amplitude is zero.
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 9dcf12c..95f6059 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -32,8 +32,7 @@
 import libcore.util.NativeAllocationRegistry;
 
 /** Controls a single vibrator. */
-// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
-public final class VibratorController {
+final class VibratorController {
     private static final String TAG = "VibratorController";
 
     private final Object mLock = new Object();
@@ -99,12 +98,12 @@
 
     static native void vibratorAlwaysOnDisable(long nativePtr, long id);
 
-    public VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
+    VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
         this(vibratorId, listener, new NativeWrapper());
     }
 
     @VisibleForTesting
-    public VibratorController(int vibratorId, OnVibrationCompleteListener listener,
+    VibratorController(int vibratorId, OnVibrationCompleteListener listener,
             NativeWrapper nativeWrapper) {
         mNativeWrapper = nativeWrapper;
         mNativeWrapper.init(vibratorId, listener);
@@ -142,11 +141,6 @@
         }
     }
 
-    @VisibleForTesting
-    public NativeWrapper getNativeWrapper() {
-        return mNativeWrapper;
-    }
-
     /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
     public VibratorInfo getVibratorInfo() {
         return mVibratorInfo;
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
similarity index 82%
rename from services/core/java/com/android/server/VibratorManagerService.java
rename to services/core/java/com/android/server/vibrator/VibratorManagerService.java
index e7e5d67..1750854 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.vibrator;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.vibrator.IVibrator;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -51,12 +56,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
-import com.android.server.vibrator.InputDeviceDelegate;
-import com.android.server.vibrator.Vibration;
-import com.android.server.vibrator.VibrationScaler;
-import com.android.server.vibrator.VibrationSettings;
-import com.android.server.vibrator.VibrationThread;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -74,6 +75,7 @@
 /** System implementation of {@link IVibratorManagerService}. */
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
+    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -89,7 +91,7 @@
         @Override
         public void onStart() {
             mService = new VibratorManagerService(getContext(), new Injector());
-            publishBinderService("vibrator_manager", mService);
+            publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
         }
 
         @Override
@@ -105,6 +107,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final String mSystemUiPackage;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
     private final Handler mHandler;
@@ -128,6 +131,26 @@
     private VibrationScaler mVibrationScaler;
     private InputDeviceDelegate mInputDeviceDelegate;
 
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                synchronized (mLock) {
+                    // When the system is entering a non-interactive state, we want
+                    // to cancel vibrations in case a misbehaving app has abandoned
+                    // them.  However it may happen that the system is currently playing
+                    // haptic feedback as part of the transition.  So we don't cancel
+                    // system vibrations.
+                    if (mCurrentVibration != null
+                            && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                    }
+                }
+            }
+        }
+    };
+
     static native long nativeInit(OnSyncedVibrationCompleteListener listener);
 
     static native long nativeGetFinalizer();
@@ -155,6 +178,9 @@
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
         mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
 
+        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+                .getSystemUiServiceComponent().getPackageName();
+
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
@@ -184,6 +210,12 @@
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).off();
         }
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(mIntentReceiver, filter);
+
+        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
@@ -320,10 +352,7 @@
                     return;
                 }
 
-                VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
-                        mBatteryStatsService, mVibrationCallbacks);
-
-                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
                 if (ignoreStatus != null) {
                     endVibrationLocked(vib, ignoreStatus);
                     return;
@@ -334,7 +363,7 @@
                     if (mCurrentVibration != null) {
                         mCurrentVibration.cancel();
                     }
-                    Vibration.Status status = startVibrationLocked(vibThread);
+                    Vibration.Status status = startVibrationLocked(vib);
                     if (status != Vibration.Status.RUNNING) {
                         endVibrationLocked(vib, status);
                     }
@@ -371,7 +400,7 @@
                         mVibratorManagerRecords.record(mCurrentExternalVibration);
                         mCurrentExternalVibration.externalVibration.mute();
                         mCurrentExternalVibration = null;
-                        // TODO(b/167946816): set external control to false
+                        setExternalControl(false);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -432,6 +461,12 @@
         }
     }
 
+    private void setExternalControl(boolean externalControl) {
+        for (int i = 0; i < mVibrators.size(); i++) {
+            mVibrators.valueAt(i).setExternalControl(externalControl);
+        }
+    }
+
     @GuardedBy("mLock")
     private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
         for (int i = 0; i < vib.effects.size(); i++) {
@@ -453,19 +488,19 @@
     }
 
     @GuardedBy("mLock")
-    private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+    private Vibration.Status startVibrationLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            Vibration vib = vibThread.getVibration();
             vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
-
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                     vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
-
             if (inputDevicesAvailable) {
                 return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
             }
 
+            VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+                    mBatteryStatsService, mVibrationCallbacks);
+
             if (mCurrentVibration == null) {
                 return startVibrationThreadLocked(vibThread);
             }
@@ -507,6 +542,12 @@
     }
 
     @GuardedBy("mLock")
+    private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
+        vib.end(status);
+        mVibratorManagerRecords.record(vib);
+    }
+
+    @GuardedBy("mLock")
     private void reportFinishedVibrationLocked(Vibration.Status status) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -551,8 +592,8 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
-        if (vibThread.getVibration().isRepeating()) {
+    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(Vibration vibration) {
+        if (vibration.isRepeating()) {
             // Repeating vibrations always take precedence.
             return null;
         }
@@ -827,6 +868,13 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    private boolean isSystemHapticFeedback(Vibration vib) {
+        if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
+            return false;
+        }
+        return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
+    }
+
     @GuardedBy("mLock")
     private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
         for (int i = 0; i < mVibrators.size(); i++) {
@@ -859,6 +907,10 @@
                 VibratorController.OnVibrationCompleteListener listener) {
             return new VibratorController(vibratorId, listener);
         }
+
+        void addService(String name, IBinder service) {
+            ServiceManager.addService(name, service);
+        }
     }
 
     /**
@@ -908,7 +960,7 @@
 
     /** Listener for synced vibration completion callbacks from native. */
     @VisibleForTesting
-    public interface OnSyncedVibrationCompleteListener {
+    interface OnSyncedVibrationCompleteListener {
 
         /** Callback triggered when synced vibration is complete. */
         void onComplete(long vibrationId);
@@ -1088,14 +1140,14 @@
                 }
                 pw.println();
                 pw.println("  mCurrentVibration:");
-                pw.println("    " + mCurrentVibration == null
-                        ? null : mCurrentVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mCurrentVibration == null
+                        ? null : mCurrentVibration.getVibration().getDebugInfo()));
                 pw.println("  mNextVibration:");
-                pw.println("    " + mNextVibration == null
-                        ? null : mNextVibration.getVibration().getDebugInfo());
+                pw.println("    " + (mNextVibration == null
+                        ? null : mNextVibration.getVibration().getDebugInfo()));
                 pw.println("  mCurrentExternalVibration:");
-                pw.println("    " + mCurrentExternalVibration == null
-                        ? null : mCurrentExternalVibration.getDebugInfo());
+                pw.println("    " + (mCurrentExternalVibration == null
+                        ? null : mCurrentExternalVibration.getDebugInfo()));
                 pw.println();
                 pw.println("  mVibrationSettings=" + mVibrationSettings);
                 for (int i = 0; i < mPreviousVibrations.size(); i++) {
@@ -1108,6 +1160,7 @@
                     }
                 }
 
+                pw.println();
                 pw.println("  Previous external vibrations:");
                 for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
                     pw.println("    " + info);
@@ -1122,52 +1175,193 @@
                 mVibrationSettings.dumpProto(proto);
                 if (mCurrentVibration != null) {
                     mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
-                            VibratorServiceDumpProto.CURRENT_VIBRATION);
+                            VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
                 }
                 if (mCurrentExternalVibration != null) {
                     mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
-                            VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+                            VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
                 }
 
                 boolean isVibrating = false;
                 boolean isUnderExternalControl = false;
                 for (int i = 0; i < mVibrators.size(); i++) {
+                    proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
                     isVibrating |= mVibrators.valueAt(i).isVibrating();
                     isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
                 }
-                proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
-                proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+                proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
+                proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
                         isUnderExternalControl);
 
-                for (Vibration.DebugInfo info : mPreviousVibrations.get(
-                        VibrationAttributes.USAGE_RINGTONE)) {
-                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
-                }
-
-                for (Vibration.DebugInfo info : mPreviousVibrations.get(
-                        VibrationAttributes.USAGE_NOTIFICATION)) {
-                    info.dumpProto(proto,
-                            VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
-                }
-
-                for (Vibration.DebugInfo info : mPreviousVibrations.get(
-                        VibrationAttributes.USAGE_ALARM)) {
-                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
-                }
-
-                for (Vibration.DebugInfo info : mPreviousVibrations.get(
-                        VibrationAttributes.USAGE_UNKNOWN)) {
-                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+                for (int i = 0; i < mPreviousVibrations.size(); i++) {
+                    long fieldId;
+                    switch (mPreviousVibrations.keyAt(i)) {
+                        case VibrationAttributes.USAGE_RINGTONE:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
+                            break;
+                        case VibrationAttributes.USAGE_NOTIFICATION:
+                            fieldId = VibratorManagerServiceDumpProto
+                                    .PREVIOUS_NOTIFICATION_VIBRATIONS;
+                            break;
+                        case VibrationAttributes.USAGE_ALARM:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
+                            break;
+                        default:
+                            fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
+                    }
+                    for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+                        info.dumpProto(proto, fieldId);
+                    }
                 }
 
                 for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                    info.dumpProto(proto,
+                            VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
                 }
             }
             proto.flush();
         }
     }
 
+    /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
+    private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+        ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
+
+        @Override
+        public int onExternalVibrationStart(ExternalVibration vib) {
+            if (!hasExternalControlCapability()) {
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+                    vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                        + " tried to play externally controlled vibration"
+                        + " without VIBRATE permission, ignoring.");
+                return IExternalVibratorService.SCALE_MUTE;
+            }
+
+            int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
+                    vib.getVibrationAttributes());
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+                vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
+                } else {
+                    endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
+                }
+                return vibHolder.scale;
+            }
+
+            VibrationThread cancelingVibration = null;
+            int scale;
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    // We are already playing this external vibration, so we can return the same
+                    // scale calculated in the previous call to this method.
+                    return mCurrentExternalVibration.scale;
+                }
+                if (mCurrentExternalVibration == null) {
+                    // If we're not under external control right now, then cancel any normal
+                    // vibration that may be playing and ready the vibrator for external control.
+                    if (mCurrentVibration != null) {
+                        mNextVibration = null;
+                        mCurrentVibration.cancel();
+                        cancelingVibration = mCurrentVibration;
+                    }
+                } else {
+                    endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+                }
+                // At this point we either have an externally controlled vibration playing, or
+                // no vibration playing. Since the interface defines that only one externally
+                // controlled vibration can play at a time, by returning something other than
+                // SCALE_MUTE from this function we can be assured that if we are currently
+                // playing vibration, it will be muted in favor of the new vibration.
+                //
+                // Note that this doesn't support multiple concurrent external controls, as we
+                // would need to mute the old one still if it came from a different controller.
+                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+                vib.linkToDeath(mCurrentExternalDeathRecipient);
+                mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
+                        vib.getVibrationAttributes().getUsage());
+                scale = mCurrentExternalVibration.scale;
+            }
+
+            if (cancelingVibration != null) {
+                try {
+                    cancelingVibration.join();
+                } catch (InterruptedException e) {
+                    Slog.w("Interrupted while waiting for vibration to finish before starting "
+                            + "external control", e);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            }
+            setExternalControl(true);
+            if (DEBUG) {
+                Slog.e(TAG, "Playing external vibration: " + vib);
+            }
+            return scale;
+        }
+
+        @Override
+        public void onExternalVibrationStop(ExternalVibration vib) {
+            synchronized (mLock) {
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    if (DEBUG) {
+                        Slog.e(TAG, "Stopping external vibration" + vib);
+                    }
+                    stopExternalVibrateLocked(Vibration.Status.FINISHED);
+                }
+            }
+        }
+
+        private void stopExternalVibrateLocked(Vibration.Status status) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
+            try {
+                if (mCurrentExternalVibration == null) {
+                    return;
+                }
+                endVibrationLocked(mCurrentExternalVibration, status);
+                mCurrentExternalVibration.externalVibration.unlinkToDeath(
+                        mCurrentExternalDeathRecipient);
+                mCurrentExternalDeathRecipient = null;
+                mCurrentExternalVibration = null;
+                setExternalControl(false);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        private boolean hasExternalControlCapability() {
+            for (int i = 0; i < mVibrators.size(); i++) {
+                if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
+            public void binderDied() {
+                synchronized (mLock) {
+                    if (mCurrentExternalVibration != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "External vibration finished because binder died");
+                        }
+                        stopExternalVibrateLocked(Vibration.Status.CANCELLED);
+                    }
+                }
+            }
+        }
+    }
+
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
         public static final String SHELL_PACKAGE_NAME = "com.android.shell";
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5697564..370d921 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -31,6 +31,7 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.ILocalWallpaperColorConsumer;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
 import android.app.PendingIntent;
@@ -59,6 +60,7 @@
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -85,7 +87,10 @@
 import android.service.wallpaper.WallpaperService;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -105,7 +110,6 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.WindowManagerInternal;
@@ -136,6 +140,8 @@
     private static final String TAG = "WallpaperManagerService";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_LIVE = true;
+    private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
+            new RectF(0, 0, 1, 1);
 
     public static class Lifecycle extends SystemService {
         private IWallpaperManagerService mService;
@@ -866,6 +872,12 @@
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
     private int mCurrentUserId = UserHandle.USER_NULL;
     private boolean mInAmbientMode;
+    private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
+            new ArrayMap<>();
+    private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
+            mLocalColorAreaCallbacks = new ArrayMap<>();
+    private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
+    private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
 
     static class WallpaperData {
 
@@ -1276,6 +1288,32 @@
         }
 
         @Override
+        public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
+                int displayId) {
+            forEachDisplayConnector(displayConnector -> {
+                if (displayConnector.mDisplayId == displayId) {
+                    RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
+                    ArrayMap<IBinder, Integer> callbackDisplayIds;
+                    synchronized (mLock) {
+                        callbacks = mLocalColorAreaCallbacks.get(area);
+                        callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
+                    }
+                    if (callbacks == null) return;
+                    callbacks.broadcast(c -> {
+                        try {
+                            int targetDisplayId =
+                                    callbackDisplayIds.get(c.asBinder());
+                            if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    });
+                }
+            });
+        }
+
+
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mLock) {
                 Slog.w(TAG, "Wallpaper service gone: " + name);
@@ -1437,6 +1475,15 @@
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to request wallpaper colors", e);
                 }
+
+                ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
+                if (areas != null && areas.size() != 0) {
+                    try {
+                        connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to register local colors areas", e);
+                    }
+                }
             }
         }
 
@@ -2340,6 +2387,115 @@
         return true;
     }
 
+    private IWallpaperEngine getEngine(int which, int userId, int displayId) {
+        WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
+        if (wallpaperData == null) return null;
+        IWallpaperEngine engine = null;
+        synchronized (mLock) {
+            for (int i = 0; i < wallpaperData.connection.mDisplayConnector.size(); i++) {
+                int id = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+                int currentWhich = wallpaperData.connection.mDisplayConnector.get(i).mDisplayId;
+                if (id != displayId && currentWhich != which) continue;
+                engine = wallpaperData.connection.mDisplayConnector.get(i).mEngine;
+                break;
+            }
+        }
+        return engine;
+    }
+
+    @Override
+    public void addOnLocalColorsChangedListener(@NonNull ILocalWallpaperColorConsumer callback,
+            @NonNull List<RectF> regions, int which, int userId, int displayId)
+            throws RemoteException {
+        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+        }
+        IWallpaperEngine engine = getEngine(which, userId, displayId);
+        if (engine == null) return;
+        ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
+        synchronized (mLock) {
+            ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
+            if (areas == null) areas = new ArraySet<>(regions.size());
+            areas.addAll(regions);
+            mLocalColorCallbackAreas.put(callback.asBinder(), areas);
+        }
+        for (int i = 0; i < regions.size(); i++) {
+            if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
+                continue;
+            }
+            RemoteCallbackList callbacks;
+            synchronized (mLock) {
+                callbacks = mLocalColorAreaCallbacks.get(
+                        regions.get(i));
+                if (callbacks == null) {
+                    callbacks = new RemoteCallbackList();
+                    mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
+                }
+                mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
+                ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+                if (displayAreas == null) {
+                    displayAreas = new ArraySet<>(1);
+                    mLocalColorDisplayIdAreas.put(displayId, displayAreas);
+                }
+                displayAreas.add(regions.get(i));
+            }
+            validAreas.add(regions.get(i));
+            callbacks.register(callback);
+        }
+        engine.addLocalColorsAreas(validAreas);
+    }
+
+    @Override
+    public void removeOnLocalColorsChangedListener(
+            @NonNull ILocalWallpaperColorConsumer callback, int which, int userId,
+            int displayId) throws RemoteException {
+        if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
+            throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
+        }
+        final UserHandle callingUser = Binder.getCallingUserHandle();
+        if (callingUser.getIdentifier() != userId) {
+            throw new SecurityException("calling user id does not match");
+        }
+        final long identity = Binder.clearCallingIdentity();
+        ArrayList<RectF> removeAreas = new ArrayList<>();
+        ArrayList<Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>>
+                callbacksToRemove = new ArrayList<>();
+        try {
+            synchronized (mLock) {
+                ArraySet<RectF> areas = mLocalColorCallbackAreas.remove(callback.asBinder());
+                mLocalColorCallbackDisplayId.remove(callback.asBinder());
+                if (areas == null) areas = new ArraySet<>();
+                for (RectF area : areas) {
+                    RemoteCallbackList callbacks = mLocalColorAreaCallbacks.get(area);
+                    if (callbacks == null) continue;
+                    callbacksToRemove.add(new Pair<>(callbacks, callback));
+                    if (callbacks.getRegisteredCallbackCount() == 0) {
+                        mLocalColorAreaCallbacks.remove(area);
+                        removeAreas.add(area);
+                    }
+                    ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
+                    if (displayAreas != null) {
+                        displayAreas.remove(area);
+                    }
+                }
+            }
+            for (int i = 0; i < callbacksToRemove.size(); i++) {
+                Pair<RemoteCallbackList, ILocalWallpaperColorConsumer>
+                        pair = callbacksToRemove.get(i);
+                pair.first.unregister(pair.second);
+            }
+        } catch (Exception e) {
+            // ignore any exception
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (removeAreas.size() == 0) return;
+        IWallpaperEngine engine = getEngine(which, userId, displayId);
+        if (engine == null) return;
+        engine.removeLocalColorsAreas(removeAreas);
+    }
+
     @Override
     public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
             throws RemoteException {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 771b712..c63a0f0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -134,10 +134,10 @@
     }
 
     @Override
-    public void activityResumed(IBinder token) {
+    public void activityResumed(IBinder token, boolean handleSplashScreenExit) {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
-            ActivityRecord.activityResumedLocked(token);
+            ActivityRecord.activityResumedLocked(token, handleSplashScreenExit);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -692,6 +692,18 @@
     }
 
     /**
+     * Splash screen view is attached to activity.
+     */
+    @Override
+    public void splashScreenAttached(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized (mGlobalLock) {
+            ActivityRecord.splashScreenAttachedLocked(token);
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    /**
      * Checks the state of the system and the activity associated with the given {@param token} to
      * verify that picture-in-picture is supported for that activity.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 68a2c5d..9bf6df4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -42,6 +42,8 @@
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
+import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -243,6 +245,7 @@
 import android.app.servertransaction.StartActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.app.servertransaction.TopResumedActivityChangeItem;
+import android.app.servertransaction.TransferSplashScreenViewStateItem;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -300,6 +303,7 @@
 import android.view.WindowManager.TransitionOldType;
 import android.view.animation.Animation;
 import android.window.IRemoteTransition;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -669,6 +673,30 @@
     boolean startingDisplayed;
     boolean startingMoved;
 
+    boolean mHandleExitSplashScreen;
+    @TransferSplashScreenState int mTransferringSplashScreenState =
+            TRANSFER_SPLASH_SCREEN_IDLE;
+
+    // Idle, can be triggered to do transfer if needed.
+    static final int TRANSFER_SPLASH_SCREEN_IDLE = 0;
+    // requesting a copy from shell.
+    static final int TRANSFER_SPLASH_SCREEN_COPYING = 1;
+    // attach the splash screen view to activity.
+    static final int TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT = 2;
+    // client has taken over splash screen view.
+    static final int TRANSFER_SPLASH_SCREEN_FINISH = 3;
+
+    @IntDef(prefix = { "TRANSFER_SPLASH_SCREEN_" }, value = {
+            TRANSFER_SPLASH_SCREEN_IDLE,
+            TRANSFER_SPLASH_SCREEN_COPYING,
+            TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT,
+            TRANSFER_SPLASH_SCREEN_FINISH,
+    })
+    @interface TransferSplashScreenState {}
+
+    // How long we wait until giving up transfer splash screen.
+    private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000;
+
     // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
     boolean mIsExiting;
     // Force an app transition to be ran in the case the visibility of the app did not change.
@@ -1727,6 +1755,7 @@
         if (_createTime > 0) {
             createTime = _createTime;
         }
+        mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
     }
 
     /**
@@ -1807,7 +1836,85 @@
         return hasProcess() && app.hasThread();
     }
 
-    boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
+    /**
+     * Evaluate the theme for a starting window.
+     * @param originalTheme The original theme which read from activity or application.
+     * @param replaceTheme The replace theme which requested from starter.
+     * @return Resolved theme.
+     */
+    private int evaluateStartingWindowTheme(String pkg, int originalTheme, int replaceTheme) {
+        // Skip if the package doesn't want a starting window.
+        if (!validateStartingWindowTheme(pkg, originalTheme)) {
+            return 0;
+        }
+        int selectedTheme = originalTheme;
+        if (replaceTheme != 0 && validateStartingWindowTheme(pkg, replaceTheme)) {
+            // allow to replace theme
+            selectedTheme = replaceTheme;
+        }
+        return selectedTheme;
+    }
+
+    private boolean validateStartingWindowTheme(String pkg, int theme) {
+        // If this is a translucent window, then don't show a starting window -- the current
+        // effect (a full-screen opaque starting window that fades away to the real contents
+        // when it is ready) does not work for this.
+        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
+        if (theme != 0) {
+            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                    com.android.internal.R.styleable.Window,
+                    mWmService.mCurrentUserId);
+            if (ent == null) {
+                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
+                // see that.
+                return false;
+            }
+            final boolean windowIsTranslucent = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+            final boolean windowIsFloating = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsFloating, false);
+            final boolean windowShowWallpaper = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+            final boolean windowDisableStarting = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
+            ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
+                    "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
+                    windowIsTranslucent, windowIsFloating, windowShowWallpaper,
+                    windowDisableStarting);
+            if (windowIsTranslucent || windowIsFloating || windowDisableStarting) {
+                return false;
+            }
+            if (windowShowWallpaper
+                    && getDisplayContent().mWallpaperController.getWallpaperTarget() != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void applyStartingWindowTheme(String pkg, int theme) {
+        if (theme != 0) {
+            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                    com.android.internal.R.styleable.Window,
+                    mWmService.mCurrentUserId);
+            if (ent == null) {
+                return;
+            }
+            final boolean windowShowWallpaper = ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
+            if (windowShowWallpaper && getDisplayContent().mWallpaperController
+                    .getWallpaperTarget() == null) {
+                // If this theme is requesting a wallpaper, and the wallpaper
+                // is not currently visible, then this effectively serves as
+                // an opaque window and our starting window transition animation
+                // can still work.  We just need to make sure the starting window
+                // is also showing the wallpaper.
+                windowFlags |= FLAG_SHOW_WALLPAPER;
+            }
+        }
+    }
+
+    boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
             CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
             IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
             boolean allowTaskSnapshot, boolean activityCreated) {
@@ -1850,49 +1957,12 @@
             return createSnapshot(snapshot, typeParameter);
         }
 
-        // If this is a translucent window, then don't show a starting window -- the current
-        // effect (a full-screen opaque starting window that fades away to the real contents
-        // when it is ready) does not work for this.
-        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
-        if (theme != 0) {
-            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
-                    com.android.internal.R.styleable.Window,
-                    mWmService.mCurrentUserId);
-            if (ent == null) {
-                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
-                // see that.
-                return false;
-            }
-            final boolean windowIsTranslucent = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
-            final boolean windowIsFloating = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowIsFloating, false);
-            final boolean windowShowWallpaper = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
-            final boolean windowDisableStarting = ent.array.getBoolean(
-                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
-            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
-                    windowIsTranslucent, windowIsFloating, windowShowWallpaper);
-            if (windowIsTranslucent) {
-                return false;
-            }
-            if (windowIsFloating || windowDisableStarting) {
-                return false;
-            }
-            if (windowShowWallpaper) {
-                if (getDisplayContent().mWallpaperController
-                        .getWallpaperTarget() == null) {
-                    // If this theme is requesting a wallpaper, and the wallpaper
-                    // is not currently visible, then this effectively serves as
-                    // an opaque window and our starting window transition animation
-                    // can still work.  We just need to make sure the starting window
-                    // is also showing the wallpaper.
-                    windowFlags |= FLAG_SHOW_WALLPAPER;
-                } else {
-                    return false;
-                }
-            }
+        // Original theme can be 0 if developer doesn't request any theme. So if resolved theme is 0
+        // but original theme is not 0, means this package doesn't want a starting window.
+        if (resolvedTheme == 0 && theme != 0) {
+            return false;
         }
+        applyStartingWindowTheme(pkg, resolvedTheme);
 
         if (transferStartingWindow(transferFrom)) {
             return true;
@@ -1906,7 +1976,7 @@
 
         ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
         mStartingData = new SplashScreenStartingData(mWmService, pkg,
-                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+                resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                 getMergedOverrideConfiguration(), typeParameter);
         scheduleAddStartingWindow();
         return true;
@@ -2031,7 +2101,118 @@
         return snapshot.getRotation() == targetRotation;
     }
 
+    /**
+     * See {@link SplashScreen#setOnExitAnimationListener}.
+     */
+    void setCustomizeSplashScreenExitAnimation(boolean enable) {
+        if (mHandleExitSplashScreen == enable) {
+            return;
+        }
+        mHandleExitSplashScreen = enable;
+    }
+
+    private final Runnable mTransferSplashScreenTimeoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mAtmService.mGlobalLock) {
+                Slog.w(TAG, "Activity transferring splash screen timeout for "
+                        + ActivityRecord.this + " state " + mTransferringSplashScreenState);
+                if (isTransferringSplashScreen()) {
+                    mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+                    // TODO show default exit splash screen animation
+                    removeStartingWindow();
+                }
+            }
+        }
+    };
+
+    private void scheduleTransferSplashScreenTimeout() {
+        mAtmService.mH.postDelayed(mTransferSplashScreenTimeoutRunnable,
+                TRANSFER_SPLASH_SCREEN_TIMEOUT);
+    }
+
+    private void removeTransferSplashScreenTimeout() {
+        mAtmService.mH.removeCallbacks(mTransferSplashScreenTimeoutRunnable);
+    }
+
+    private boolean transferSplashScreenIfNeeded() {
+        if (!mHandleExitSplashScreen || mStartingSurface == null || mStartingWindow == null
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+            return false;
+        }
+        if (isTransferringSplashScreen()) {
+            return true;
+        }
+        requestCopySplashScreen();
+        return isTransferringSplashScreen();
+    }
+
+    private boolean isTransferringSplashScreen() {
+        return mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+    }
+
+    private void requestCopySplashScreen() {
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
+        if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+            removeStartingWindow();
+        }
+        scheduleTransferSplashScreenTimeout();
+    }
+
+    /**
+     * Receive the splash screen data from shell, sending to client.
+     * @param parcelable The data to reconstruct the splash screen view, null mean unable to copy.
+     */
+    void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
+        removeTransferSplashScreenTimeout();
+        // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
+        // either way, abort and reset the sequence.
+        if (parcelable == null
+                || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+            if (parcelable != null) {
+                parcelable.clearIfNeeded();
+            }
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+            removeStartingWindow();
+            return;
+        }
+        // schedule attach splashScreen to client
+        try {
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                    TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+            scheduleTransferSplashScreenTimeout();
+        } catch (Exception e) {
+            Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+            parcelable.clearIfNeeded();
+            mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+        }
+    }
+
+    private void onSplashScreenAttachComplete() {
+        removeTransferSplashScreenTimeout();
+        // Client has draw the splash screen, so we can remove the starting window.
+        if (mStartingWindow != null) {
+            mStartingWindow.hide(false, false);
+        }
+        try {
+            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                    TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
+        } catch (Exception e) {
+            Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
+        }
+        // no matter what, remove the starting window.
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
+        removeStartingWindow();
+    }
+
     void removeStartingWindow() {
+        if (transferSplashScreenIfNeeded()) {
+            return;
+        }
+        mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
         if (mStartingWindow == null) {
             if (mStartingData != null) {
                 // Starting window has not been added yet, but it is scheduled to be added.
@@ -5092,7 +5273,7 @@
         }
     }
 
-    static void activityResumedLocked(IBinder token) {
+    static void activityResumedLocked(IBinder token, boolean handleSplashScreenExit) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r);
         if (r == null) {
@@ -5100,12 +5281,22 @@
             // been removed (e.g. destroy timeout), so the token could be null.
             return;
         }
+        r.setCustomizeSplashScreenExitAnimation(handleSplashScreenExit);
         r.setSavedState(null /* savedState */);
 
         r.mDisplayContent.handleActivitySizeCompatModeIfNeeded(r);
         r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
     }
 
+    static void splashScreenAttachedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        if (r == null) {
+            Slog.w(TAG, "splashScreenTransferredLocked cannot find activity");
+            return;
+        }
+        r.onSplashScreenAttachComplete();
+    }
+
     /**
      * Once we know that we have asked an application to put an activity in the resumed state
      * (either by launching it or explicitly telling it), this function updates the rest of our
@@ -5187,6 +5378,7 @@
             }
         }
 
+        mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
         mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
@@ -5931,7 +6123,13 @@
         pendingVoiceInteractionStart = false;
     }
 
-    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+    void showStartingWindow(boolean taskSwitch) {
+        showStartingWindow(null /* prev */, false /* newTask */, taskSwitch,
+                0 /* splashScreenTheme */);
+    }
+
+    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+            int splashScreenTheme) {
         if (mTaskOverlay) {
             // We don't show starting window for overlay activities.
             return;
@@ -5944,7 +6142,10 @@
 
         final CompatibilityInfo compatInfo =
                 mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
-        final boolean shown = addStartingWindow(packageName, theme,
+
+        final int resolvedTheme = evaluateStartingWindowTheme(packageName, theme,
+                splashScreenTheme);
+        final boolean shown = addStartingWindow(packageName, resolvedTheme,
                 compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                 prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                 allowTaskSnapshot(),
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 3456e51..37fda4c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -772,11 +772,6 @@
             newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
         }
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, new IntentSender(target));
-        ActivityOptions options = mRequest.activityOptions.getOptions(mRequest.intent,
-                mRequest.activityInfo,
-                mService.getProcessController(mRequest.caller),
-                mSupervisor);
-        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_ACTIVITY_OPTIONS, options.toBundle());
         heavy.updateIntentForHeavyWeightActivity(newIntent);
         newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
                 mRequest.activityInfo.packageName);
@@ -2002,8 +1997,7 @@
 
         if (mMovedToFront) {
             // We moved the task to front, use starting window to hide initial drawn delay.
-            targetTaskTop.showStartingWindow(null /* prev */, false /* newTask */,
-                    true /* taskSwitch */);
+            targetTaskTop.showStartingWindow(true /* taskSwitch */);
         } else if (mDoResume) {
             // Make sure the root task and its belonging display are moved to topmost.
             mTargetRootTask.moveToFront("intentActivityFound");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 7d2075c..94379b1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -574,4 +574,26 @@
      * @return Whether the package is the base of any locked task
      */
     public abstract boolean isBaseOfLockedTask(String packageName);
+
+    /**
+     * Create an interface to update configuration for an application.
+     */
+    public abstract PackageConfigurationUpdater createPackageConfigurationUpdater();
+
+    /**
+     * An interface to update configuration for an application, and will persist override
+     * configuration for this package.
+     */
+    public interface PackageConfigurationUpdater {
+        /**
+         * Sets the dark mode for the current application. This setting is persisted and will
+         * override the system configuration for this application.
+         */
+        PackageConfigurationUpdater setNightMode(int nightMode);
+
+        /**
+         * Commit changes.
+         */
+        void commit() throws RemoteException;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f97af62..2e98c2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -224,6 +224,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 import android.window.IWindowOrganizerController;
+import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerTransaction;
 
@@ -451,6 +452,7 @@
     /** The controller for all operations related to locktask. */
     private LockTaskController mLockTaskController;
     private ActivityStartController mActivityStartController;
+    PackageConfigPersister mPackageConfigPersister;
 
     boolean mSuppressResizeConfigChanges;
 
@@ -788,7 +790,7 @@
         final boolean sizeCompatFreeform = Settings.Global.getInt(
                 resolver, DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM, 0) != 0;
         final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(
-                resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+                resolver, DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
 
         // Transfer any global setting for forcing RTL layout, into a System Property
         DisplayProperties.debug_force_rtl(forceRtl);
@@ -866,6 +868,7 @@
         setRecentTasks(new RecentTasks(this, mTaskSupervisor));
         mVrController = new VrController(mGlobalLock);
         mKeyguardController = mTaskSupervisor.getKeyguardController();
+        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
     }
 
     public void onActivityManagerInternalAdded() {
@@ -2027,8 +2030,7 @@
 
                 // We are reshowing a task, use a starting window to hide the initial draw delay
                 // so the transition can start earlier.
-                topActivity.showStartingWindow(null /* prev */, false /* newTask */,
-                        true /* taskSwitch */);
+                topActivity.showStartingWindow(true /* taskSwitch */);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -3249,6 +3251,30 @@
     }
 
     /**
+     * A splash screen view has copied, pass it to an activity.
+     *
+     * @param taskId Id of task to handle the material to reconstruct the view.
+     * @param parcelable Used to reconstruct the view, null means the surface is un-copyable.
+     * @hide
+     */
+    @Override
+    public void onSplashScreenViewCopyFinished(int taskId, SplashScreenViewParcelable parcelable)
+            throws RemoteException {
+        mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_TASKS,
+                "copySplashScreenViewFinish()");
+        synchronized (mGlobalLock) {
+            final Task task = mRootWindowContainer.anyTaskForId(taskId,
+                    MATCH_ATTACHED_TASK_ONLY);
+            if (task != null) {
+                final ActivityRecord r = task.getTopWaitSplashScreenActivity();
+                if (r != null) {
+                    r.onCopySplashScreenFinish(parcelable);
+                }
+            }
+        }
+    }
+
+    /**
      * Puts the given activity in picture in picture mode if possible.
      *
      * @return true if the activity is now in picture-in-picture mode, or false if it could not
@@ -5433,6 +5459,7 @@
             synchronized (mGlobalLock) {
                 mAppWarnings.onPackageUninstalled(name);
                 mCompatModePackages.handlePackageUninstalledLocked(name);
+                mPackageConfigPersister.onPackageUninstall(name);
             }
         }
 
@@ -6103,6 +6130,7 @@
         public void removeUser(int userId) {
             synchronized (mGlobalLock) {
                 mRootWindowContainer.removeUser(userId);
+                mPackageConfigPersister.removeUser(userId);
             }
         }
 
@@ -6200,6 +6228,8 @@
         public void loadRecentTasksForUser(int userId) {
             synchronized (mGlobalLock) {
                 mRecentTasks.loadUserRecentsLocked(userId);
+                // TODO renaming the methods(?)
+                mPackageConfigPersister.loadUserPackages(userId);
             }
         }
 
@@ -6308,5 +6338,54 @@
                 return getLockTaskController().isBaseOfLockedTask(packageName);
             }
         }
+
+        @Override
+        public PackageConfigurationUpdater createPackageConfigurationUpdater() {
+            synchronized (mGlobalLock) {
+                return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+            }
+        }
+    }
+
+    final class PackageConfigurationUpdaterImpl implements
+            ActivityTaskManagerInternal.PackageConfigurationUpdater {
+        private int mPid;
+        private int mNightMode;
+
+        PackageConfigurationUpdaterImpl(int pid) {
+            mPid = pid;
+        }
+
+        @Override
+        public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+            mNightMode = nightMode;
+            return this;
+        }
+
+        @Override
+        public void commit() throws RemoteException {
+            if (mPid == 0) {
+                throw new RemoteException("Invalid process");
+            }
+            synchronized (mGlobalLock) {
+                final WindowProcessController wpc = mProcessMap.getProcess(mPid);
+                if (wpc == null) {
+                    Slog.w(TAG, "Override application configuration: cannot find application");
+                    return;
+                }
+                if (wpc.getNightMode() == mNightMode) {
+                    return;
+                }
+                if (!wpc.setOverrideNightMode(mNightMode)) {
+                    return;
+                }
+                wpc.updateNightModeForAllActivities(mNightMode);
+                mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
+            }
+        }
+
+        int getNightMode() {
+            return mNightMode;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d90e885..efcaaa4 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,6 +534,28 @@
         return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
     }
 
+    /**
+     * Overrides the night mode applied to this ConfigurationContainer.
+     * @return true if the nightMode has been changed.
+     */
+    public boolean setOverrideNightMode(int nightMode) {
+        final int currentUiMode = mFullConfiguration.uiMode;
+        final int currentNightMode = getNightMode();
+        final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
+        if (currentNightMode == validNightMode) {
+            return false;
+        }
+        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+        mRequestsTmpConfig.uiMode = validNightMode
+                | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        return true;
+    }
+
+    int getNightMode() {
+        return mFullConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+    }
+
     public boolean isActivityTypeDream() {
         return getActivityType() == ACTIVITY_TYPE_DREAM;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f075d85..759b7fe 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -429,6 +429,10 @@
 
     void setOrganizer(IDisplayAreaOrganizer organizer, boolean skipDisplayAreaAppeared) {
         if (mOrganizer == organizer) return;
+        if (mDisplayContent == null || !mDisplayContent.isTrusted()) {
+            throw new IllegalStateException(
+                    "Don't organize or trigger events for unavailable or untrusted display.");
+        }
         IDisplayAreaOrganizer lastOrganizer = mOrganizer;
         // Update the new display area organizer before calling sendDisplayAreaVanished since it
         // could result in a new SurfaceControl getting created that would notify the old organizer
@@ -500,6 +504,17 @@
         return false;
     }
 
+    @Override
+    void removeImmediately() {
+        setOrganizer(null);
+        super.removeImmediately();
+    }
+
+    @Override
+    DisplayArea getDisplayArea() {
+        return this;
+    }
+
     /**
      * DisplayArea that contains WindowTokens, and orders them according to their type.
      */
@@ -580,11 +595,6 @@
         }
     }
 
-    @Override
-    DisplayArea getDisplayArea() {
-        return this;
-    }
-
     /**
      * DisplayArea that can be dimmed.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index ed44876..2beb378 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -21,6 +21,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.DisplayArea.Type.ANY;
 
+import android.annotation.Nullable;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
@@ -77,6 +78,11 @@
         mService.enforceTaskPermission(func);
     }
 
+    @Nullable
+    IDisplayAreaOrganizer getOrganizerByFeature(int featureId) {
+        return mOrganizersByFeatureIds.get(featureId);
+    }
+
     @Override
     public ParceledListSlice<DisplayAreaAppearedInfo> registerOrganizer(
             IDisplayAreaOrganizer organizer, int feature) {
@@ -100,10 +106,18 @@
                 }
 
                 final List<DisplayAreaAppearedInfo> displayAreaInfos = new ArrayList<>();
-                mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
-                    if (da.mFeatureId != feature) return;
-                    displayAreaInfos.add(organizeDisplayArea(organizer, da,
-                            "DisplayAreaOrganizerController.registerOrganizer"));
+                mService.mRootWindowContainer.forAllDisplays(dc -> {
+                    if (!dc.isTrusted()) {
+                        ProtoLog.w(WM_DEBUG_WINDOW_ORGANIZER,
+                                "Don't organize or trigger events for untrusted displayId=%d",
+                                dc.getDisplayId());
+                        return;
+                    }
+                    dc.forAllDisplayAreas((da) -> {
+                        if (da.mFeatureId != feature) return;
+                        displayAreaInfos.add(organizeDisplayArea(organizer, da,
+                                "DisplayAreaOrganizerController.registerOrganizer"));
+                    });
                 });
 
                 mOrganizersByFeatureIds.put(feature, organizer);
@@ -148,6 +162,10 @@
                     throw new IllegalArgumentException("createTaskDisplayArea unknown displayId="
                             + displayId);
                 }
+                if (!display.isTrusted()) {
+                    throw new IllegalArgumentException("createTaskDisplayArea untrusted displayId="
+                            + displayId);
+                }
 
                 // The parentFeatureId can be either a RootDisplayArea or a TaskDisplayArea.
                 // Check if there is a RootDisplayArea with the given parentFeatureId.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 23eab98..86968ed 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -213,6 +213,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.IDisplayAreaOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -239,6 +240,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -662,12 +664,8 @@
      */
     private boolean mRemoved;
 
-    /**
-     * Non-null if the last size compatibility mode activity is using non-native screen
-     * configuration. The activity is not able to put in multi-window mode, so it exists only one
-     * per display.
-     */
-    private ActivityRecord mLastCompatModeActivity;
+    /** Set of activities in foreground size compat mode. */
+    private Set<ActivityRecord> mActiveSizeCompatActivities = new ArraySet<>();
 
     // Used in updating the display size
     private Point mTmpDisplaySize = new Point();
@@ -1074,6 +1072,7 @@
 
         // Sets the display content for the children.
         onDisplayChanged(this);
+        updateDisplayAreaOrganizers();
 
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
@@ -2712,6 +2711,30 @@
     }
 
     /**
+     * Checks for all non-organized {@link DisplayArea}s for if there is any existing organizer for
+     * their features. If so, registers them with the matched organizer.
+     */
+    @VisibleForTesting
+    void updateDisplayAreaOrganizers() {
+        if (!isTrusted()) {
+            // No need to update for untrusted display.
+            return;
+        }
+        forAllDisplayAreas(displayArea -> {
+            if (displayArea.isOrganized()) {
+                return;
+            }
+            // Check if we have a registered organizer for the DA feature.
+            final IDisplayAreaOrganizer organizer =
+                    mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+                            .getOrganizerByFeature(displayArea.mFeatureId);
+            if (organizer != null) {
+                displayArea.setOrganizer(organizer);
+            }
+        });
+    }
+
+    /**
      * Returns true if the input point is within an app window.
      */
     boolean pointWithinAppWindow(int x, int y) {
@@ -5527,24 +5550,23 @@
     /** Checks whether the given activity is in size compatibility mode and notifies the change. */
     void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
         final Task organizedTask = r.getOrganizedTask();
-        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
-                || organizedTask == null) {
-            // The callback is only interested in the foreground changes of fullscreen activity.
+        if (organizedTask == null) {
+            mActiveSizeCompatActivities.remove(r);
             return;
         }
-        // TODO(b/178327644) Update for per Task size compat
-        if (!r.inSizeCompatMode()) {
-            if (mLastCompatModeActivity != null) {
+
+        if (r.isState(RESUMED) && r.inSizeCompatMode()) {
+            if (mActiveSizeCompatActivities.add(r)) {
+                // Trigger task event for new size compat activity.
                 organizedTask.onSizeCompatActivityChanged();
             }
-            mLastCompatModeActivity = null;
             return;
         }
-        if (mLastCompatModeActivity == r) {
-            return;
+
+        if (mActiveSizeCompatActivities.remove(r)) {
+            // Trigger task event for activity no longer in foreground size compat.
+            organizedTask.onSizeCompatActivityChanged();
         }
-        mLastCompatModeActivity = r;
-        organizedTask.onSizeCompatActivityChanged();
     }
 
     boolean isUidPresent(int uid) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 737f810..8fcdf2e 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -109,12 +109,13 @@
      */
     void setBaseSettingsFilePath(@Nullable String path) {
         AtomicFile settingsFile;
-        if (path != null) {
-            settingsFile = new AtomicFile(new File(path), WM_DISPLAY_COMMIT_TAG);
+        File file = path != null ? new File(path) : null;
+        if (file != null && file.exists()) {
+            settingsFile = new AtomicFile(file, WM_DISPLAY_COMMIT_TAG);
         } else {
+            Slog.w(TAG, "display settings " + path + " does not exist, using vendor defaults");
             settingsFile = getVendorSettingsFile();
         }
-
         setBaseSettingsStorage(new AtomicFileStorage(settingsFile));
     }
 
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
new file mode 100644
index 0000000..1552a96
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -0,0 +1,380 @@
+/*
+ * 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.wm;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+
+import android.annotation.NonNull;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+/**
+ * Persist configuration for each package, only persist the change if some on attributes are
+ * different from the global configuration. This class only applies to packages with Activities.
+ */
+public class PackageConfigPersister {
+    private static final String TAG = PackageConfigPersister.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String TAG_CONFIG = "config";
+    private static final String ATTR_PACKAGE_NAME = "package_name";
+    private static final String ATTR_NIGHT_MODE = "night_mode";
+
+    private static final String PACKAGE_DIRNAME = "package_configs";
+    private static final String SUFFIX_FILE_NAME = "_config.xml";
+
+    private final PersisterQueue mPersisterQueue;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
+            new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<HashMap<String, PackageConfigRecord>> mModified =
+            new SparseArray<>();
+
+    private static File getUserConfigsDir(int userId) {
+        return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
+    }
+
+    PackageConfigPersister(PersisterQueue queue) {
+        mPersisterQueue = queue;
+    }
+
+    @GuardedBy("mLock")
+    void loadUserPackages(int userId) {
+        synchronized (mLock) {
+            final File userConfigsDir = getUserConfigsDir(userId);
+            final File[] configFiles = userConfigsDir.listFiles();
+            if (configFiles == null) {
+                Slog.v(TAG, "loadPackages: empty list files from " + userConfigsDir);
+                return;
+            }
+
+            for (int fileIndex = 0; fileIndex < configFiles.length; ++fileIndex) {
+                final File configFile = configFiles[fileIndex];
+                if (DEBUG) {
+                    Slog.d(TAG, "loadPackages: userId=" + userId
+                            + ", configFile=" + configFile.getName());
+                }
+                if (!configFile.getName().endsWith(SUFFIX_FILE_NAME)) {
+                    continue;
+                }
+
+                try (InputStream is = new FileInputStream(configFile)) {
+                    final TypedXmlPullParser in = Xml.resolvePullParser(is);
+                    int event;
+                    String packageName = null;
+                    int nightMode = MODE_NIGHT_AUTO;
+                    while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+                            && event != XmlPullParser.END_TAG) {
+                        final String name = in.getName();
+                        if (event == XmlPullParser.START_TAG) {
+                            if (DEBUG) {
+                                Slog.d(TAG, "loadPackages: START_TAG name=" + name);
+                            }
+                            if (TAG_CONFIG.equals(name)) {
+                                for (int attIdx = in.getAttributeCount() - 1; attIdx >= 0;
+                                        --attIdx) {
+                                    final String attrName = in.getAttributeName(attIdx);
+                                    final String attrValue = in.getAttributeValue(attIdx);
+                                    switch (attrName) {
+                                        case ATTR_PACKAGE_NAME:
+                                            packageName = attrValue;
+                                            break;
+                                        case ATTR_NIGHT_MODE:
+                                            nightMode = Integer.parseInt(attrValue);
+                                            break;
+                                    }
+                                }
+                            }
+                        }
+                        XmlUtils.skipCurrentTag(in);
+                    }
+                    if (packageName != null) {
+                        final PackageConfigRecord initRecord =
+                                findRecordOrCreate(mModified, packageName, userId);
+                        initRecord.mNightMode = nightMode;
+                        if (DEBUG) {
+                            Slog.d(TAG, "loadPackages: load one package " + initRecord);
+                        }
+                    }
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } catch (XmlPullParserException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void updateConfigIfNeeded(@NonNull ConfigurationContainer container, int userId,
+            String packageName) {
+        synchronized (mLock) {
+            final PackageConfigRecord modifiedRecord = findRecord(mModified, packageName, userId);
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
+            }
+            if (modifiedRecord != null) {
+                container.setOverrideNightMode(modifiedRecord.mNightMode);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void updateFromImpl(String packageName, int userId,
+            ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+        synchronized (mLock) {
+            PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
+            record.mNightMode = impl.getNightMode();
+
+            if (record.isResetNightMode()) {
+                removePackage(record.mName, record.mUserId);
+            } else {
+                final PackageConfigRecord pendingRecord =
+                        findRecord(mPendingWrite, record.mName, record.mUserId);
+                final PackageConfigRecord writeRecord;
+                if (pendingRecord == null) {
+                    writeRecord = findRecordOrCreate(mPendingWrite, record.mName,
+                            record.mUserId);
+                } else {
+                    writeRecord = pendingRecord;
+                }
+                if (writeRecord.mNightMode == record.mNightMode) {
+                    return;
+                }
+                writeRecord.mNightMode = record.mNightMode;
+                if (DEBUG) {
+                    Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
+                }
+                mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    void removeUser(int userId) {
+        synchronized (mLock) {
+            final HashMap<String, PackageConfigRecord> modifyRecords = mModified.get(userId);
+            final HashMap<String, PackageConfigRecord> writeRecords = mPendingWrite.get(userId);
+            if ((modifyRecords == null || modifyRecords.size() == 0)
+                    && (writeRecords == null || writeRecords.size() == 0)) {
+                return;
+            }
+            final HashMap<String, PackageConfigRecord> tempList = new HashMap<>(modifyRecords);
+            tempList.forEach((name, record) -> {
+                removePackage(record.mName, record.mUserId);
+            });
+        }
+    }
+
+    @GuardedBy("mLock")
+    void onPackageUninstall(String packageName) {
+        synchronized (mLock) {
+            for (int i = mModified.size() - 1; i > 0; i--) {
+                final int userId = mModified.keyAt(i);
+                removePackage(packageName, userId);
+            }
+        }
+    }
+
+    private void removePackage(String packageName, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "removePackage packageName :" + packageName + " userId " + userId);
+        }
+        final PackageConfigRecord record = findRecord(mPendingWrite, packageName, userId);
+        if (record != null) {
+            removeRecord(mPendingWrite, record);
+            mPersisterQueue.removeItems(item ->
+                            item.mRecord.mName == record.mName
+                                    && item.mRecord.mUserId == record.mUserId,
+                    WriteProcessItem.class);
+        }
+
+        final PackageConfigRecord modifyRecord = findRecord(mModified, packageName, userId);
+        if (modifyRecord != null) {
+            removeRecord(mModified, modifyRecord);
+            mPersisterQueue.addItem(new DeletePackageItem(userId, packageName),
+                    false /* flush */);
+        }
+    }
+
+    // store a changed data so we don't need to get the process
+    static class PackageConfigRecord {
+        final String mName;
+        final int mUserId;
+        int mNightMode;
+
+        PackageConfigRecord(String name, int userId) {
+            mName = name;
+            mUserId = userId;
+        }
+
+        boolean isResetNightMode() {
+            return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+        }
+
+        @Override
+        public String toString() {
+            return "PackageConfigRecord package name: " + mName + " userId " + mUserId
+                    + " nightMode " + mNightMode;
+        }
+    }
+
+    private PackageConfigRecord findRecordOrCreate(
+            SparseArray<HashMap<String, PackageConfigRecord>> list, String name, int userId) {
+        HashMap<String, PackageConfigRecord> records = list.get(userId);
+        if (records == null) {
+            records = new HashMap<>();
+            list.put(userId, records);
+        }
+        PackageConfigRecord record = records.get(name);
+        if (record != null) {
+            return record;
+        }
+        record = new PackageConfigRecord(name, userId);
+        records.put(name, record);
+        return record;
+    }
+
+    private PackageConfigRecord findRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+            String name, int userId) {
+        HashMap<String, PackageConfigRecord> packages = list.get(userId);
+        if (packages == null) {
+            return null;
+        }
+        return packages.get(name);
+    }
+
+    private void removeRecord(SparseArray<HashMap<String, PackageConfigRecord>> list,
+            PackageConfigRecord record) {
+        final HashMap<String, PackageConfigRecord> processes = list.get(record.mUserId);
+        if (processes != null) {
+            processes.remove(record.mName);
+        }
+    }
+
+    private static class DeletePackageItem implements PersisterQueue.WriteQueueItem {
+        final int mUserId;
+        final String mPackageName;
+
+        DeletePackageItem(int userId, String packageName) {
+            mUserId = userId;
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void process() {
+            File userConfigsDir = getUserConfigsDir(mUserId);
+            if (!userConfigsDir.isDirectory()) {
+                return;
+            }
+            final AtomicFile atomicFile = new AtomicFile(new File(userConfigsDir,
+                    mPackageName + SUFFIX_FILE_NAME));
+            if (atomicFile.exists()) {
+                atomicFile.delete();
+            }
+        }
+    }
+
+    private class WriteProcessItem implements PersisterQueue.WriteQueueItem {
+        final PackageConfigRecord mRecord;
+
+        WriteProcessItem(PackageConfigRecord record) {
+            mRecord = record;
+        }
+
+        @Override
+        public void process() {
+            // Write out one user.
+            byte[] data = null;
+            synchronized (mLock) {
+                try {
+                    data = saveToXml();
+                } catch (Exception e) {
+                }
+                removeRecord(mPendingWrite, mRecord);
+            }
+            if (data != null) {
+                // Write out xml file while not holding mService lock.
+                FileOutputStream file = null;
+                AtomicFile atomicFile = null;
+                try {
+                    File userConfigsDir = getUserConfigsDir(mRecord.mUserId);
+                    if (!userConfigsDir.isDirectory() && !userConfigsDir.mkdirs()) {
+                        Slog.e(TAG, "Failure creating tasks directory for user " + mRecord.mUserId
+                                + ": " + userConfigsDir);
+                        return;
+                    }
+                    atomicFile = new AtomicFile(new File(userConfigsDir,
+                            mRecord.mName + SUFFIX_FILE_NAME));
+                    file = atomicFile.startWrite();
+                    file.write(data);
+                    atomicFile.finishWrite(file);
+                } catch (IOException e) {
+                    if (file != null) {
+                        atomicFile.failWrite(file);
+                    }
+                    Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
+                }
+            }
+        }
+
+        private byte[] saveToXml() throws IOException {
+            final ByteArrayOutputStream os = new ByteArrayOutputStream();
+            final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os);
+
+            xmlSerializer.startDocument(null, true);
+            if (DEBUG) {
+                Slog.d(TAG, "Writing package configuration=" + mRecord);
+            }
+            xmlSerializer.startTag(null, TAG_CONFIG);
+            xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
+            xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            xmlSerializer.endTag(null, TAG_CONFIG);
+            xmlSerializer.endDocument();
+            xmlSerializer.flush();
+
+            return os.toByteArray();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6e8110e..d81181d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -58,6 +58,8 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -1886,6 +1888,7 @@
         // Fill in some deprecated values.
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
+        rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ceebe95..6fbeaa4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2656,7 +2656,7 @@
     void addStartingWindowsForVisibleActivities() {
         forAllActivities((r) -> {
             if (r.mVisibleRequested) {
-                r.showStartingWindow(null /* prev */, false /* newTask */, true /*taskSwitch*/);
+                r.showStartingWindow(true /*taskSwitch*/);
             }
         });
     }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 37bb274..8b18679 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -248,11 +248,6 @@
     }
 
     @Override
-    public void setTransparentRegion(IWindow window, Region region) {
-        mService.setTransparentRegionWindow(this, window, region);
-    }
-
-    @Override
     public void setInsets(IWindow window, int touchableInsets,
             Rect contentInsets, Rect visibleInsets, Region touchableArea) {
         mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 94e14dd..ef4a40f 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -60,7 +60,7 @@
 
         final Task task = activity.getTask();
         if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
-                activity.token)) {
+                activity.token, theme)) {
             return new ShellStartingSurface(task);
         }
         return null;
@@ -125,7 +125,8 @@
             return mService.mTaskSnapshotController
                     .createStartingSurface(activity, taskSnapshot);
         }
-        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token);
+        mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token,
+                0 /* launchTheme */);
         return new ShellStartingSurface(task);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9bbbbe0..8bd4dfd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -82,6 +82,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -158,6 +159,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -293,6 +295,9 @@
     private static final String ATTR_MIN_HEIGHT = "min_height";
     private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
     private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
+    private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size";
+    private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
+    private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
 
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
@@ -540,6 +545,10 @@
     // NOTE: This value needs to be persisted with each task
     private TaskDescription mTaskDescription;
 
+    // Information about the last snapshot that should be persisted with the task to allow SystemUI
+    // to layout without loading all the task snapshots
+    final PersistedTaskSnapshotData mLastTaskSnapshotData;
+
     // If set to true, the task will report that it is not in the floating
     // state regardless of it's root task affiliation. As the floating state drives
     // production of content insets this can be used to preserve them across
@@ -613,8 +622,6 @@
     SurfaceControl.Transaction mMainWindowSizeChangeTransaction;
     Task mMainWindowSizeChangeTask;
 
-    Rect mPreAnimationBounds = new Rect();
-
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -839,13 +846,13 @@
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
             boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
             String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
-            TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
-            int nextTaskId, int callingUid, String callingPackage,
-            @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture,
-            boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
-            ActivityInfo info, IVoiceInteractionSession _voiceSession,
-            IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer,
-            IBinder _launchCookie, boolean _deferTaskAppear) {
+            TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
+            int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
+            String callingPackage, @Nullable String callingFeatureId, int resizeMode,
+            boolean supportsPictureInPicture, boolean _realActivitySuspended,
+            boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info,
+            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+            boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear) {
         super(atmService.mWindowManager);
 
         mAtmService = atmService;
@@ -855,7 +862,12 @@
         mUserId = _userId;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
-        mTaskDescription = _lastTaskDescription;
+        mTaskDescription = _lastTaskDescription != null
+                ? _lastTaskDescription
+                : new TaskDescription();
+        mLastTaskSnapshotData = _lastSnapshotData != null
+                ? _lastSnapshotData
+                : new PersistedTaskSnapshotData();
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
         mRemoteToken = new RemoteToken(this);
@@ -3870,6 +3882,13 @@
         });
     }
 
+    ActivityRecord getTopWaitSplashScreenActivity() {
+        return getActivity((r) -> {
+            return r.mHandleExitSplashScreen
+                    && r.mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_COPYING;
+        });
+    }
+
     boolean isTopActivityFocusable() {
         final ActivityRecord r = topRunningActivity();
         return r != null ? r.isFocusable()
@@ -3899,6 +3918,7 @@
     }
 
     void onSnapshotChanged(TaskSnapshot snapshot) {
+        mLastTaskSnapshotData.set(snapshot);
         mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(
                 mTaskId, snapshot);
     }
@@ -4157,7 +4177,6 @@
     void fillTaskInfo(TaskInfo info, boolean stripExtras) {
         getNumRunningActivities(mReuseActivitiesReport);
         info.userId = isLeafTask() ? mUserId : mCurrentUser;
-        info.stackId = getRootTaskId();
         info.taskId = mTaskId;
         info.displayId = getDisplayId();
         info.isRunning = getTopNonFinishingActivity() != null;
@@ -4256,6 +4275,9 @@
             if (mainWindow != null) {
                 info.mainWindowLayoutParams = mainWindow.getAttrs();
             }
+            // If the developer has persist a different configuration, we need to override it to the
+            // starting window because persisted configuration does not effect to Task.
+            info.taskInfo.configuration.setTo(topActivity.getConfiguration());
         }
         final ActivityRecord topFullscreenActivity = getTopFullscreenActivity();
         if (topFullscreenActivity != null) {
@@ -4707,6 +4729,19 @@
         out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight);
         out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION);
 
+        if (mLastTaskSnapshotData.taskSize != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE,
+                    mLastTaskSnapshotData.taskSize.flattenToString());
+        }
+        if (mLastTaskSnapshotData.contentInsets != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS,
+                    mLastTaskSnapshotData.contentInsets.flattenToString());
+        }
+        if (mLastTaskSnapshotData.bufferSize != null) {
+            out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE,
+                    mLastTaskSnapshotData.bufferSize.flattenToString());
+        }
+
         if (affinityIntent != null) {
             out.startTag(null, TAG_AFFINITYINTENT);
             affinityIntent.saveToXml(out);
@@ -4774,6 +4809,7 @@
         int taskId = INVALID_TASK_ID;
         final int outerDepth = in.getDepth();
         TaskDescription taskDescription = new TaskDescription();
+        PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
         int taskAffiliation = INVALID_TASK_ID;
         int prevTaskId = INVALID_TASK_ID;
         int nextTaskId = INVALID_TASK_ID;
@@ -4883,6 +4919,15 @@
                 case ATTR_PERSIST_TASK_VERSION:
                     persistTaskVersion = Integer.parseInt(attrValue);
                     break;
+                case ATTR_LAST_SNAPSHOT_TASK_SIZE:
+                    lastSnapshotData.taskSize = Point.unflattenFromString(attrValue);
+                    break;
+                case ATTR_LAST_SNAPSHOT_CONTENT_INSETS:
+                    lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue);
+                    break;
+                case ATTR_LAST_SNAPSHOT_BUFFER_SIZE:
+                    lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue);
+                    break;
                 default:
                     if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
                         Slog.w(TAG, "Task: Unknown attribute=" + attrName);
@@ -4977,6 +5022,7 @@
                 .setLastTimeMoved(lastTimeOnTop)
                 .setNeverRelinquishIdentity(neverRelinquishIdentity)
                 .setLastTaskDescription(taskDescription)
+                .setLastSnapshotData(lastSnapshotData)
                 .setTaskAffiliation(taskAffiliation)
                 .setPrevAffiliateTaskId(prevTaskId)
                 .setNextAffiliateTaskId(nextTaskId)
@@ -6523,10 +6569,9 @@
                 Slog.i(TAG, "Restarting because process died: " + next);
                 if (!next.hasBeenLaunched) {
                     next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+                } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
                         && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
+                    next.showStartingWindow(false /* taskSwitch */);
                 }
                 mTaskSupervisor.startSpecificActivity(next, true, false);
                 return true;
@@ -6549,8 +6594,7 @@
                 next.hasBeenLaunched = true;
             } else {
                 if (SHOW_APP_STARTING_PREVIEW) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwich */);
+                    next.showStartingWindow(false /* taskSwich */);
                 }
                 if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
             }
@@ -6711,7 +6755,10 @@
                         prev = null;
                     }
                 }
-                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
+                final int splashScreenThemeResId = options != null
+                        ? options.getSplashScreenThemeResId() : 0;
+                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
+                        splashScreenThemeResId);
             }
         } else {
             // If this is the first activity, don't do any fancy animations,
@@ -7904,6 +7951,7 @@
         private long mLastTimeMoved;
         private boolean mNeverRelinquishIdentity;
         private TaskDescription mLastTaskDescription;
+        private PersistedTaskSnapshotData mLastSnapshotData;
         private int mTaskAffiliation;
         private int mPrevAffiliateTaskId = INVALID_TASK_ID;
         private int mNextAffiliateTaskId = INVALID_TASK_ID;
@@ -8104,6 +8152,11 @@
             return this;
         }
 
+        private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) {
+            mLastSnapshotData = lastSnapshotData;
+            return this;
+        }
+
         private Builder setOrigActivity(ComponentName origActivity) {
             mOrigActivity = origActivity;
             return this;
@@ -8217,9 +8270,6 @@
             mCallingPackage = mActivityInfo.packageName;
             mResizeMode = mActivityInfo.resizeMode;
             mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
-            if (mLastTaskDescription == null) {
-                mLastTaskDescription = new TaskDescription();
-            }
 
             final Task task = buildInner();
             task.mHasBeenVisible = mHasBeenVisible;
@@ -8253,9 +8303,9 @@
             return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
                     mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
                     mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
-                    mNeverRelinquishIdentity, mLastTaskDescription, mTaskAffiliation,
-                    mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid, mCallingPackage,
-                    mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
+                    mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
+                    mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
+                    mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
                     mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
                     mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,
                     mLaunchCookie, mDeferTaskAppear);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9fac3f0..c0bce6b 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -117,8 +117,11 @@
             return mTaskOrganizer.asBinder();
         }
 
-        void addStartingWindow(Task task, IBinder appToken) {
+        void addStartingWindow(Task task, IBinder appToken, int launchTheme) {
             final StartingWindowInfo info = task.getStartingWindowInfo();
+            if (launchTheme != 0) {
+                info.splashScreenThemeResId = launchTheme;
+            }
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
                 try {
                     mTaskOrganizer.addStartingWindow(info, appToken);
@@ -138,6 +141,16 @@
             });
         }
 
+        void copySplashScreenView(Task task) {
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.copySplashScreenView(task.mTaskId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
+                }
+            });
+        }
+
         SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
             SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
             if (!task.mCreatedByOrganizer && !visible) {
@@ -232,14 +245,18 @@
             mUid = uid;
         }
 
-        void addStartingWindow(Task t, IBinder appToken) {
-            mOrganizer.addStartingWindow(t, appToken);
+        void addStartingWindow(Task t, IBinder appToken, int launchTheme) {
+            mOrganizer.addStartingWindow(t, appToken, launchTheme);
         }
 
         void removeStartingWindow(Task t) {
             mOrganizer.removeStartingWindow(t);
         }
 
+        void copySplashScreenView(Task t) {
+            mOrganizer.copySplashScreenView(t);
+        }
+
         /**
          * Register this task with this state, but doesn't trigger the task appeared callback to
          * the organizer.
@@ -465,14 +482,14 @@
         return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
     }
 
-    boolean addStartingWindow(Task task, IBinder appToken) {
+    boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null || rootTask.mTaskOrganizer == null) {
             return false;
         }
         final TaskOrganizerState state =
                 mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
-        state.addStartingWindow(task, appToken);
+        state.addStartingWindow(task, appToken, launchTheme);
         return true;
     }
 
@@ -486,6 +503,17 @@
         state.removeStartingWindow(task);
     }
 
+    boolean copySplashScreenView(Task task) {
+        final Task rootTask = task.getRootTask();
+        if (rootTask == null || rootTask.mTaskOrganizer == null) {
+            return false;
+        }
+        final TaskOrganizerState state =
+                mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
+        state.copySplashScreenView(task);
+        return true;
+    }
+
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         if (state != null && state.addTask(task)) {
@@ -646,7 +674,7 @@
             // Remove and add for re-ordering.
             mPendingTaskEvents.remove(pending);
         }
-        pending.mForce = force;
+        pending.mForce |= force;
         mPendingTaskEvents.add(pending);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3c7bab3..5dc5ab7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -977,7 +977,7 @@
         void updateSupportsNonResizableMultiWindow() {
             ContentResolver resolver = mContext.getContentResolver();
             final boolean supportsNonResizableMultiWindow = Settings.Global.getInt(resolver,
-                    DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 0) != 0;
+                    DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 1) != 0;
 
             mAtmService.mSupportsNonResizableMultiWindow = supportsNonResizableMultiWindow;
         }
@@ -2150,23 +2150,6 @@
         Slog.i(tag, s, e);
     }
 
-    void setTransparentRegionWindow(Session session, IWindow client, Region region) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                WindowState w = windowForClientLocked(session, client, false);
-                ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE transparentRegionHint=%s: %s",
-                        region, w);
-
-                if ((w != null) && w.mHasSurface) {
-                    w.mWinAnimator.setTransparentRegionHintLocked(region);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
     void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
             Rect visibleInsets, Region touchableRegion) {
         int uid = Binder.getCallingUid();
@@ -7531,8 +7514,8 @@
                 if (spec != null) {
                     result.setTo(spec);
                 }
-                spec.scale *= windowState.mGlobalScale;
-                return spec;
+                result.scale *= windowState.mGlobalScale;
+                return result;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 264a3b4..c362023 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -256,6 +256,7 @@
         }
 
         onConfigurationChanged(atm.getGlobalConfiguration());
+        mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
     }
 
     public void setPid(int pid) {
@@ -802,6 +803,13 @@
         return false;
     }
 
+    void updateNightModeForAllActivities(int nightMode) {
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            r.setOverrideNightMode(nightMode);
+        }
+    }
+
     public void clearPackagePreferredForHomeActivities() {
         synchronized (mAtm.mGlobalLock) {
             for (int i = mActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fe70dc1..ece256e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -678,14 +678,6 @@
         }
     }
 
-    void setTransparentRegionHintLocked(final Region region) {
-        if (mSurfaceController == null) {
-            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
-            return;
-        }
-        mSurfaceController.setTransparentRegionHint(region);
-    }
-
     boolean setWallpaperOffset(int dx, int dy, float scale) {
         if (mXOffset == dx && mYOffset == dy && Float.compare(mWallpaperScale, scale) == 0) {
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 82ba3c1..636f0bb 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -194,22 +194,6 @@
         return true;
     }
 
-    void setTransparentRegionHint(final Region region) {
-        if (mSurfaceControl == null) {
-            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
-            return;
-        }
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
-        mService.openSurfaceTransaction();
-        try {
-            getGlobalTransaction().setTransparentRegionHint(mSurfaceControl, region);
-        } finally {
-            mService.closeSurfaceTransaction("setTransparentRegion");
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    "<<< CLOSE TRANSACTION setTransparentRegion");
-        }
-    }
-
     void setOpaque(boolean isOpaque) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 91be056..11e3ecf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -30,6 +30,7 @@
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
+        "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
         "com_android_server_input_InputManagerService.cpp",
@@ -54,12 +55,12 @@
         "com_android_server_UsbMidiDevice.cpp",
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_vibrator_VibratorController.cpp",
-        "com_android_server_VibratorManagerService.cpp",
+        "com_android_server_vibrator_VibratorManagerService.cpp",
         "com_android_server_PersistentDataBlockService.cpp",
-        "com_android_server_am_CachedAppOptimizer.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "onload.cpp",
+        ":lib_cachedAppOptimizer_native",
         ":lib_networkStatsFactory_native",
     ],
 
@@ -149,7 +150,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power-V1-cpp",
         "android.hardware.power.stats@1.0",
-        "android.hardware.power.stats-ndk_platform",
+        "android.hardware.power.stats-V1-ndk_platform",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.input@1.0",
         "android.hardware.vibrator-V2-cpp",
@@ -193,3 +194,10 @@
         "com_android_server_net_NetworkStatsFactory.cpp",
     ],
 }
+
+filegroup {
+    name: "lib_cachedAppOptimizer_native",
+    srcs: [
+        "com_android_server_am_CachedAppOptimizer.cpp",
+    ],
+}
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 9a8942b..d076434 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,10 +1,6 @@
 # Display
 per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
 
-# Haptics
-per-file com_android_server_vibrator_VibratorController.cpp = michaelwr@google.com
-per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
-
 # Input
 per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com, svv@google.com
 
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 31cc295..4551d49 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -278,6 +278,11 @@
     return retVal;
 }
 
+static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
+                                                                            jobject clazz) {
+    return env->NewStringUTF(CGROUP_FREEZE_PATH);
+}
+
 static const JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
         {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
@@ -286,7 +291,9 @@
          (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
         {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
         {"getBinderFreezeInfo", "(I)I",
-         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}};
+         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
+        {"getFreezerCheckPath", "()Ljava/lang/String;",
+         (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
diff --git a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
similarity index 100%
rename from packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp
rename to services/core/jni/com_android_server_connectivity_Vpn.cpp
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 4e47984..89b931d 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -29,7 +29,7 @@
 
 #include <vibratorservice/VibratorHalController.h>
 
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
@@ -73,11 +73,8 @@
               static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK));
 
 static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) {
-    // TODO(b/167946816): remove this once VibratorService is removed.
-    if (vibratorId < 0) {
-        return std::move(std::make_unique<vibrator::HalController>());
-    }
-    vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager();
+    vibrator::ManagerHalController* manager =
+            android_server_vibrator_VibratorManagerService_getManager();
     if (manager == nullptr) {
         return nullptr;
     }
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
similarity index 89%
rename from services/core/jni/com_android_server_VibratorManagerService.cpp
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index 5dbb71a..a47ab9d 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -26,7 +26,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
-#include "com_android_server_VibratorManagerService.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
 
 namespace android {
 
@@ -64,7 +64,7 @@
     const jobject mCallbackListener;
 };
 
-vibrator::ManagerHalController* android_server_VibratorManagerService_getManager() {
+vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager() {
     std::lock_guard<std::mutex> lock(gManagerMutex);
     return gManager;
 }
@@ -156,10 +156,11 @@
     service->hal()->cancelSynced();
 }
 
+inline static constexpr auto sNativeInitMethodSignature =
+        "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+
 static const JNINativeMethod method_table[] = {
-        {"nativeInit",
-         "(Lcom/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener;)J",
-         (void*)nativeInit},
+        {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
         {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
         {"nativeGetCapabilities", "(J)J", (void*)nativeGetCapabilities},
         {"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
@@ -168,15 +169,15 @@
         {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
 };
 
-int register_android_server_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
+int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
     sJvm = jvm;
     auto listenerClassName =
-            "com/android/server/VibratorManagerService$OnSyncedVibrationCompleteListener";
+            "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
     jclass listenerClass = FindClassOrDie(env, listenerClassName);
     sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
 
-    return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
-                                    NELEM(method_table));
+    return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
+                                    method_table, NELEM(method_table));
 }
 
 }; // namespace android
diff --git a/services/core/jni/com_android_server_VibratorManagerService.h b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
similarity index 84%
rename from services/core/jni/com_android_server_VibratorManagerService.h
rename to services/core/jni/com_android_server_vibrator_VibratorManagerService.h
index 22950c5..9924e24 100644
--- a/services/core/jni/com_android_server_VibratorManagerService.h
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 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.
@@ -21,7 +21,7 @@
 
 namespace android {
 
-extern vibrator::ManagerHalController* android_server_VibratorManagerService_getManager();
+extern vibrator::ManagerHalController* android_server_vibrator_VibratorManagerService_getManager();
 
 } // namespace android
 
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 34f6048..1815f0c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -39,8 +39,9 @@
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
-int register_android_server_VibratorManagerService(JavaVM* vm, JNIEnv* env);
+int register_android_server_vibrator_VibratorManagerService(JavaVM* vm, JNIEnv* env);
 int register_android_server_location_GnssLocationProvider(JNIEnv* env);
+int register_android_server_connectivity_Vpn(JNIEnv* env);
 int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
 int register_android_server_tv_TvUinputBridge(JNIEnv* env);
 int register_android_server_tv_TvInputHal(JNIEnv* env);
@@ -54,10 +55,8 @@
 int register_android_server_security_VerityUtils(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
-        JNIEnv* env);
-int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
-    JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
@@ -90,9 +89,10 @@
     register_android_server_UsbHostManager(env);
     register_android_server_vr_VrManagerService(env);
     register_android_server_vibrator_VibratorController(vm, env);
-    register_android_server_VibratorManagerService(vm, env);
+    register_android_server_vibrator_VibratorManagerService(vm, env);
     register_android_server_SystemServer(env);
     register_android_server_location_GnssLocationProvider(env);
+    register_android_server_connectivity_Vpn(env);
     register_android_server_devicepolicy_CryptoTestHelper(env);
     register_android_server_ConsumerIrService(env);
     register_android_server_BatteryStatsService(env);
@@ -109,10 +109,8 @@
     register_android_server_security_VerityUtils(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
-    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
-            env);
-    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
-        env);
+    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
+    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
index e27e1b8..1406dbb 100644
--- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -27,6 +27,13 @@
         <xs:attribute type="xs:boolean" name="enabled" use="required" />
     </xs:complexType>
 
+    <xs:complexType name="raw-override-value">
+        <xs:attribute type="xs:string" name="packageName" use="required" />
+        <xs:attribute type="xs:long" name="minVersionCode" />
+        <xs:attribute type="xs:long" name="maxVersionCode" />
+        <xs:attribute type="xs:boolean" name="enabled" use="required" />
+    </xs:complexType>
+
     <xs:complexType name="change-overrides">
         <xs:attribute type="xs:long" name="changeId" use="required"/>
         <xs:element name="validated">
@@ -43,6 +50,13 @@
                 </xs:sequence>
             </xs:complexType>
         </xs:element>
+        <xs:element name="raw">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
     </xs:complexType>
 
     <xs:element name="overrides">
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
index 08b8207..a5ccffc 100644
--- a/services/core/xsd/platform-compat/overrides/schema/current.txt
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -5,9 +5,11 @@
     ctor public ChangeOverrides();
     method public long getChangeId();
     method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+    method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw();
     method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
     method public void setChangeId(long);
     method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+    method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw);
     method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
   }
 
@@ -16,6 +18,11 @@
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
   }
 
+  public static class ChangeOverrides.Raw {
+    ctor public ChangeOverrides.Raw();
+    method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue();
+  }
+
   public static class ChangeOverrides.Validated {
     ctor public ChangeOverrides.Validated();
     method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
@@ -34,6 +41,18 @@
     method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
   }
 
+  public class RawOverrideValue {
+    ctor public RawOverrideValue();
+    method public boolean getEnabled();
+    method public long getMaxVersionCode();
+    method public long getMinVersionCode();
+    method public String getPackageName();
+    method public void setEnabled(boolean);
+    method public void setMaxVersionCode(long);
+    method public void setMinVersionCode(long);
+    method public void setPackageName(String);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 59b7367..e76597d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -138,6 +138,7 @@
     private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
     private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
             "admin-can-grant-sensors-permissions";
+    private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -281,6 +282,9 @@
     public String mEnrollmentSpecificId;
     public boolean mAdminCanGrantSensorsPermissions;
 
+    private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
+    boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
+
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
         this.info = info;
         this.isParent = isParent;
@@ -548,6 +552,9 @@
         }
         writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
                 mAdminCanGrantSensorsPermissions);
+        if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
+            writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
+        }
     }
 
     void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -800,6 +807,9 @@
             } else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) {
                 mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE,
                         false);
+            } else if (TAG_USB_DATA_SIGNALING.equals(tag)) {
+                mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
+                        USB_DATA_SIGNALING_ENABLED_DEFAULT);
             } else {
                 Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1154,5 +1164,8 @@
 
         pw.print("mAdminCanGrantSensorsPermissions");
         pw.println(mAdminCanGrantSensorsPermissions);
+
+        pw.print("mUsbDataSignaling=");
+        pw.println(mUsbDataSignalingEnabled);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 404b0cf..9772c2e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -62,6 +62,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
 import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
@@ -92,7 +93,6 @@
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -157,9 +157,9 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.DeviceStateCache;
@@ -214,6 +214,7 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.hardware.usb.UsbManager;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -781,8 +782,7 @@
              * however it's too early in the boot process to register with IIpConnectivityMetrics
              * to listen for events.
              */
-            if (Intent.ACTION_USER_STARTED.equals(action)
-                    && userHandle == mOwners.getDeviceOwnerUserId()) {
+            if (Intent.ACTION_USER_STARTED.equals(action) && userHandle == UserHandle.USER_SYSTEM) {
                 synchronized (getLockObject()) {
                     if (isNetworkLoggingEnabledInternalLocked()) {
                         setNetworkLoggingActiveInternal(true);
@@ -1101,7 +1101,7 @@
      */
     private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
         int reason = getUnsafeOperationReason(operation);
-        if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+        if (reason == OPERATION_SAFETY_REASON_NONE) return;
 
         if (mSafetyChecker == null) {
             // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
@@ -1114,23 +1114,28 @@
     /**
      * Returns whether it's safe to execute the given {@code operation}, and why.
      */
-    @UnsafeOperationReason
+    @OperationSafetyReason
     int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
-        return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+        return mSafetyChecker == null ? OPERATION_SAFETY_REASON_NONE
                 : mSafetyChecker.getUnsafeOperationReason(operation);
     }
 
     @Override
     public void setNextOperationSafety(@DevicePolicyOperation int operation,
-            @UnsafeOperationReason int reason) {
+            @OperationSafetyReason int reason) {
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
         Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
                 DevicePolicyManager.operationToString(operation),
-                DevicePolicyManager.unsafeOperationReasonToString(reason)));
+                DevicePolicyManager.operationSafetyReasonToString(reason)));
         mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
     }
 
+    @Override
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        return mSafetyChecker == null ? true : mSafetyChecker.isSafeOperation(reason);
+    }
+
     // Used by DevicePolicyManagerServiceShellCommand
     List<OwnerDto> listAllOwners() {
         Preconditions.checkCallAuthorization(
@@ -1352,6 +1357,10 @@
             return mContext.getSystemService(WifiManager.class);
         }
 
+        UsbManager getUsbManager() {
+            return mContext.getSystemService(UsbManager.class);
+        }
+
         @SuppressWarnings("AndroidFrameworkBinderIdentity")
         long binderClearCallingIdentity() {
             return Binder.clearCallingIdentity();
@@ -2923,6 +2932,7 @@
 
             revertTransferOwnershipIfNecessaryLocked();
         }
+        updateUsbDataSignal();
     }
 
     private void revertTransferOwnershipIfNecessaryLocked() {
@@ -6367,7 +6377,7 @@
         Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         return mInjector.binderWithCleanCallingIdentity(
-                () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
+                () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(
                         caller.getUserId()));
     }
 
@@ -7522,19 +7532,37 @@
         sendActiveAdminCommand(action, extras, deviceOwnerUserId, receiverComponent);
     }
 
-    private void sendProfileOwnerCommand(String action, Bundle extras, int userHandle) {
-        sendActiveAdminCommand(action, extras, userHandle,
-                mOwners.getProfileOwnerComponent(userHandle));
+    void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
+        ComponentName receiverComponent = null;
+        if (action.equals(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE)) {
+            receiverComponent = resolveDelegateReceiver(DELEGATION_NETWORK_LOGGING, action, userId);
+        }
+        if (receiverComponent == null) {
+            receiverComponent = getOwnerComponent(userId);
+        }
+        sendActiveAdminCommand(action, extras, userId, receiverComponent);
+    }
+
+    private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) {
+        sendActiveAdminCommand(action, extras, userId,
+                mOwners.getProfileOwnerComponent(userId));
     }
 
     private void sendActiveAdminCommand(String action, Bundle extras,
-            int userHandle, ComponentName receiverComponent) {
+            @UserIdInt int userId, ComponentName receiverComponent) {
+        if (VERBOSE_LOG) {
+            Slog.v(LOG_TAG, "sending intent " + action + " to "
+                    + receiverComponent.flattenToShortString() + " on user " + userId);
+        }
         final Intent intent = new Intent(action);
         intent.setComponent(receiverComponent);
         if (extras != null) {
             intent.putExtras(extras);
         }
-        mContext.sendBroadcastAsUser(intent, UserHandle.of(userHandle));
+        mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
     }
 
     private void sendOwnerChangedBroadcast(String broadcast, int userId) {
@@ -8342,6 +8370,7 @@
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
         applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        setNetworkLoggingActiveInternal(false);
     }
 
     @Override
@@ -12224,6 +12253,32 @@
                             packageName, findInteractAcrossProfilesResetMode(packageName), userId);
         }
 
+        @Override
+        public void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker, int reason,
+                boolean isSafe) {
+            // TODO(b/178494483): use EventLog instead
+            // TODO(b/178494483): log metrics?
+            if (VERBOSE_LOG) {
+                Slog.v(LOG_TAG, String.format("notifyUnsafeOperationStateChanged(): %s=%b",
+                        DevicePolicyManager.operationSafetyReasonToString(reason), isSafe));
+            }
+
+            Preconditions.checkArgument(mSafetyChecker == checker,
+                    "invalid checker: should be %s, was %s", mSafetyChecker, checker);
+
+            Bundle extras = new Bundle();
+            extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason);
+            extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe);
+
+            // TODO(b/178494483): add CTS test
+            sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                    extras);
+            for (int profileOwnerId : mOwners.getProfileOwnerKeys()) {
+                sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED,
+                        extras, profileOwnerId);
+            }
+        }
+
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
             return getDefaultCrossProfilePackages().contains(packageName)
                     ? AppOpsManager.MODE_ALLOWED
@@ -14096,7 +14151,9 @@
             return;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
 
         synchronized (getLockObject()) {
@@ -14104,11 +14161,11 @@
                 // already in the requested state
                 return;
             }
-            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            deviceOwner.isNetworkLoggingEnabled = enabled;
+            final ActiveAdmin activeAdmin = getDeviceOrProfileOwnerAdminLocked(caller.getUserId());
+            activeAdmin.isNetworkLoggingEnabled = enabled;
             if (!enabled) {
-                deviceOwner.numNetworkLoggingNotifications = 0;
-                deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+                activeAdmin.numNetworkLoggingNotifications = 0;
+                activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
             }
             saveSettingsLocked(caller.getUserId());
             setNetworkLoggingActiveInternal(enabled);
@@ -14126,7 +14183,13 @@
         synchronized (getLockObject()) {
             mInjector.binderWithCleanCallingIdentity(() -> {
                 if (active) {
-                    mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal());
+                    if (mNetworkLogger == null) {
+                        final int affectedUserId = getNetworkLoggingAffectedUser();
+                        mNetworkLogger = new NetworkLogger(this,
+                                mInjector.getPackageManagerInternal(),
+                                affectedUserId == UserHandle.USER_SYSTEM
+                                        ? UserHandle.USER_ALL : affectedUserId);
+                    }
                     if (!mNetworkLogger.startNetworkLogging()) {
                         mNetworkLogger = null;
                         Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
@@ -14146,6 +14209,25 @@
         }
     }
 
+    private @UserIdInt int getNetworkLoggingAffectedUser() {
+        synchronized (getLockObject()) {
+            if (mOwners.hasDeviceOwner()) {
+                return mOwners.getDeviceOwnerUserId();
+            } else {
+                return mInjector.binderWithCleanCallingIdentity(
+                        () -> getManagedUserId(UserHandle.USER_SYSTEM));
+            }
+        }
+    }
+
+    private ActiveAdmin getNetworkLoggingControllingAdminLocked() {
+        int affectedUserId = getNetworkLoggingAffectedUser();
+        if (affectedUserId < 0) {
+            return null;
+        }
+        return getDeviceOrProfileOwnerAdminLocked(affectedUserId);
+    }
+
     @Override
     public long forceNetworkLogs() {
         Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()),
@@ -14166,10 +14248,12 @@
     @GuardedBy("getLockObject()")
     private void maybePauseDeviceWideLoggingLocked() {
         if (!areAllUsersAffiliatedWithDeviceLocked()) {
-            Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
-                    + "paused if enabled.");
-            if (mNetworkLogger != null) {
-                mNetworkLogger.pause();
+            if (mOwners.hasDeviceOwner()) {
+                Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
+                        + "paused if enabled.");
+                if (mNetworkLogger != null) {
+                    mNetworkLogger.pause();
+                }
             }
             if (!isOrganizationOwnedDeviceWithManagedProfile()) {
                 Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
@@ -14188,7 +14272,9 @@
             if (allUsersAffiliated || orgOwnedProfileDevice) {
                 mSecurityLogMonitor.resume();
             }
-            if (allUsersAffiliated) {
+            // If there is no device owner, then per-user network logging may be enabled for the
+            // managed profile. In which case, all users do not need to be affiliated.
+            if (allUsersAffiliated || !mOwners.hasDeviceOwner()) {
                 if (mNetworkLogger != null) {
                     mNetworkLogger.resume();
                 }
@@ -14215,7 +14301,9 @@
             return false;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
                 || hasCallingOrSelfPermission(permission.MANAGE_USERS));
 
@@ -14225,8 +14313,8 @@
     }
 
     private boolean isNetworkLoggingEnabledInternalLocked() {
-        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+        ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        return (activeAdmin != null) && activeAdmin.isNetworkLoggingEnabled;
     }
 
     /*
@@ -14243,9 +14331,13 @@
             return null;
         }
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        Preconditions.checkCallAuthorization((caller.hasAdminComponent() &&  isDeviceOwner(caller))
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                &&  (isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
-        checkAllUsersAreAffiliatedWithDevice();
+        if (mOwners.hasDeviceOwner()) {
+            checkAllUsersAreAffiliatedWithDevice();
+        }
 
         synchronized (getLockObject()) {
             if (mNetworkLogger == null || !isNetworkLoggingEnabledInternalLocked()) {
@@ -14258,34 +14350,35 @@
                     .write();
 
             final long currentTime = System.currentTimeMillis();
-            DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+            DevicePolicyData policyData = getUserData(caller.getUserId());
             if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
                 policyData.mLastNetworkLogsRetrievalTime = currentTime;
-                saveSettingsLocked(UserHandle.USER_SYSTEM);
+                saveSettingsLocked(caller.getUserId());
             }
             return mNetworkLogger.retrieveLogs(batchToken);
         }
     }
 
     private void sendNetworkLoggingNotificationLocked() {
-        final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-        if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) {
+        ensureLocked();
+        final ActiveAdmin activeAdmin = getNetworkLoggingControllingAdminLocked();
+        if (activeAdmin == null || !activeAdmin.isNetworkLoggingEnabled) {
             return;
         }
-        if (deviceOwner.numNetworkLoggingNotifications >=
-                ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
+        if (activeAdmin.numNetworkLoggingNotifications
+                >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
             return;
         }
         final long now = System.currentTimeMillis();
-        if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
+        if (now - activeAdmin.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) {
             return;
         }
-        deviceOwner.numNetworkLoggingNotifications++;
-        if (deviceOwner.numNetworkLoggingNotifications
+        activeAdmin.numNetworkLoggingNotifications++;
+        if (activeAdmin.numNetworkLoggingNotifications
                 >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = 0;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = 0;
         } else {
-            deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
+            activeAdmin.lastNetworkLoggingNotificationTimeMs = now;
         }
         final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
         final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
@@ -14305,7 +14398,7 @@
                         .bigText(mContext.getString(R.string.network_logging_notification_text)))
                 .build();
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification);
-        saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+        saveSettingsLocked(activeAdmin.getUserHandle().getIdentifier());
     }
 
     /**
@@ -14369,8 +14462,12 @@
     @Override
     public long getLastNetworkLogRetrievalTime() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
-        return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
+
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))
+                || canManageUsers(caller));
+        final int affectedUserId = getNetworkLoggingAffectedUser();
+        return affectedUserId >= 0 ? getUserData(affectedUserId).mLastNetworkLogsRetrievalTime : -1;
     }
 
     @Override
@@ -15985,6 +16082,9 @@
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams,
             @NonNull String callerPackage) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null");
+        Objects.requireNonNull(callerPackage, "callerPackage is null");
+
         final ComponentName admin = provisioningParams.getProfileAdminComponentName();
         Objects.requireNonNull(admin, "admin is null");
 
@@ -15992,6 +16092,8 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
+        provisioningParams.logParams(callerPackage);
+
         UserInfo userInfo = null;
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -16291,9 +16393,12 @@
 
     @Override
     public void provisionFullyManagedDevice(
-            FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
-        ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
+            @NonNull FullyManagedDeviceProvisioningParams provisioningParams,
+            @NonNull String callerPackage) {
+        Objects.requireNonNull(provisioningParams, "provisioningParams is null.");
+        Objects.requireNonNull(callerPackage, "callerPackage is null.");
 
+        ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
         Objects.requireNonNull(deviceAdmin, "admin is null.");
         Objects.requireNonNull(provisioningParams.getOwnerName(), "owner name is null.");
 
@@ -16301,6 +16406,8 @@
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
+        provisioningParams.logParams(callerPackage);
+
         final long identity = Binder.clearCallingIdentity();
         try {
             // TODO(b/178187130): This check fails silent provisioning, uncomment once silent
@@ -16318,8 +16425,10 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
+            final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    ? UserHandle.USER_SYSTEM : caller.getUserId();
             if (!removeNonRequiredAppsForManagedDevice(
-                    caller.getUserId(),
+                    deviceOwnerUserId,
                     provisioningParams.isLeaveAllSystemAppsEnabled(),
                     deviceAdmin)) {
                 throw new ServiceSpecificException(
@@ -16327,15 +16436,16 @@
                         "PackageManager failed to remove non required apps.");
             }
 
+
             if (!setActiveAdminAndDeviceOwner(
-                    caller.getUserId(), deviceAdmin, provisioningParams.getOwnerName())) {
+                    deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
                 throw new ServiceSpecificException(
                         PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
                         "Failed to set device owner.");
             }
 
             disallowAddUser();
-            setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+            setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
         } catch (Exception e) {
             DevicePolicyEventLogger
@@ -16378,30 +16488,42 @@
     }
 
     private boolean removeNonRequiredAppsForManagedDevice(
-            int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
+            @UserIdInt int userId, boolean leaveAllSystemAppsEnabled, ComponentName admin) {
         Set<String> packagesToDelete = leaveAllSystemAppsEnabled
                 ? Collections.emptySet()
                 : mOverlayPackagesProvider.getNonRequiredApps(
                         admin, userId, ACTION_PROVISION_MANAGED_DEVICE);
+
+        removeNonInstalledPackages(packagesToDelete, userId);
         if (packagesToDelete.isEmpty()) {
+            Slog.i(LOG_TAG, "No packages to delete on user " + userId);
             return true;
         }
+
         NonRequiredPackageDeleteObserver packageDeleteObserver =
                 new NonRequiredPackageDeleteObserver(packagesToDelete.size());
         for (String packageName : packagesToDelete) {
-            if (isPackageInstalledForUser(packageName, userId)) {
-                Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
-                mContext.getPackageManager().deletePackageAsUser(
-                        packageName,
-                        packageDeleteObserver,
-                        PackageManager.DELETE_SYSTEM_APP,
-                        userId);
-            }
+            Slog.i(LOG_TAG, "Deleting package [" + packageName + "] as user " + userId);
+            mContext.getPackageManager().deletePackageAsUser(
+                    packageName,
+                    packageDeleteObserver,
+                    PackageManager.DELETE_SYSTEM_APP,
+                    userId);
         }
         Slog.i(LOG_TAG, "Waiting for non required apps to be deleted");
         return packageDeleteObserver.awaitPackagesDeletion();
     }
 
+    private void removeNonInstalledPackages(Set<String> packages, @UserIdInt int userId) {
+        final Set<String> toBeRemoved = new HashSet<>();
+        for (String packageName : packages) {
+            if (!isPackageInstalledForUser(packageName, userId)) {
+                toBeRemoved.add(packageName);
+            }
+        }
+        packages.removeAll(toBeRemoved);
+    }
+
     private void disallowAddUser() {
         if (mInjector.userManagerIsHeadlessSystemUserMode()) {
             Slog.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode.");
@@ -16507,4 +16629,78 @@
 
         return mPolicyCache.canAdminGrantSensorsPermissionsForUser(userId);
     }
+
+    @Override
+    public void setUsbDataSignalingEnabled(String packageName, boolean enabled) {
+        Objects.requireNonNull(packageName, "Admin package name must be provided");
+        final CallerIdentity caller = getCallerIdentity(packageName);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "USB data signaling can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+        Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+                "USB data signaling cannot be disabled.");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            if (admin.mUsbDataSignalingEnabled != enabled) {
+                admin.mUsbDataSignalingEnabled = enabled;
+                saveSettingsLocked(caller.getUserId());
+                updateUsbDataSignal();
+            }
+        }
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
+                .setAdmin(packageName)
+                .setBoolean(enabled)
+                .write();
+    }
+
+    private void updateUsbDataSignal() {
+        if (!canUsbDataSignalingBeDisabled()) {
+            return;
+        }
+        final boolean usbEnabled;
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            usbEnabled = admin != null && admin.mUsbDataSignalingEnabled;
+        }
+        if (!mInjector.binderWithCleanCallingIdentity(
+                () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) {
+            Slog.w(LOG_TAG, "Failed to set usb data signaling state");
+        }
+    }
+
+    @Override
+    public boolean isUsbDataSignalingEnabled(String packageName) {
+        final CallerIdentity caller = getCallerIdentity(packageName);
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "USB data signaling can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            return admin.mUsbDataSignalingEnabled;
+        }
+    }
+
+    @Override
+    public boolean isUsbDataSignalingEnabledForUser(int userId) {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isSystemUid(caller));
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            return admin != null && admin.mUsbDataSignalingEnabled;
+        }
+    }
+
+    @Override
+    public boolean canUsbDataSignalingBeDisabled() {
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 222c987..5484a14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -27,6 +27,7 @@
 final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
 
     private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe";
+    private static final String CMD_IS_SAFE_OPERATION_BY_REASON = "is-operation-safe-by-reason";
     private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe";
     private static final String CMD_LIST_OWNERS = "list-owners";
 
@@ -53,6 +54,8 @@
             switch (cmd) {
                 case CMD_IS_SAFE_OPERATION:
                     return runIsSafeOperation(pw);
+                case CMD_IS_SAFE_OPERATION_BY_REASON:
+                    return runIsSafeOperationByReason(pw);
                 case CMD_SET_SAFE_OPERATION:
                     return runSetSafeOperation(pw);
                 case CMD_LIST_OWNERS:
@@ -73,12 +76,13 @@
         return -1;
     }
 
-
     private void showHelp(PrintWriter pw) {
         pw.printf("  help\n");
         pw.printf("    Prints this help text.\n\n");
         pw.printf("  %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
         pw.printf("    Checks if the give operation is safe \n\n");
+        pw.printf("  %s <REASON_ID>\n", CMD_IS_SAFE_OPERATION_BY_REASON);
+        pw.printf("    Checks if the operations are safe for the given reason\n\n");
         pw.printf("  %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
         pw.printf("    Emulates the result of the next call to check if the given operation is safe"
                 + " \n\n");
@@ -89,10 +93,19 @@
     private int runIsSafeOperation(PrintWriter pw) {
         int operation = Integer.parseInt(getNextArgRequired());
         int reason = mService.getUnsafeOperationReason(operation);
-        boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+        boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
         pw.printf("Operation %s is %b. Reason: %s\n",
                 DevicePolicyManager.operationToString(operation), safe,
-                DevicePolicyManager.unsafeOperationReasonToString(reason));
+                DevicePolicyManager.operationSafetyReasonToString(reason));
+        return 0;
+    }
+
+    private int runIsSafeOperationByReason(PrintWriter pw) {
+        int reason = Integer.parseInt(getNextArgRequired());
+        boolean safe = mService.isSafeOperation(reason);
+        pw.printf("Operations affected by %s are %s\n",
+                DevicePolicyManager.operationSafetyReasonToString(reason),
+                (safe ? "SAFE" : "UNSAFE"));
         return 0;
     }
 
@@ -102,7 +115,7 @@
         mService.setNextOperationSafety(operation, reason);
         pw.printf("Next call to check operation %s will return %s\n",
                 DevicePolicyManager.operationToString(operation),
-                DevicePolicyManager.unsafeOperationReasonToString(reason));
+                DevicePolicyManager.operationSafetyReasonToString(reason));
         return 0;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index e9b2d7f..8843a5d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
 
@@ -47,6 +48,10 @@
     private final PackageManagerInternal mPm;
     private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false);
 
+    // The target userId to collect network events on. The target userId will be
+    // {@link android.os.UserHandle#USER_ALL} if network events should be collected for all users.
+    private final int mTargetUserId;
+
     private IIpConnectivityMetrics mIpConnectivityMetrics;
     private ServiceThread mHandlerThread;
     private NetworkLoggingHandler mNetworkLoggingHandler;
@@ -58,6 +63,11 @@
             if (!mIsLoggingEnabled.get()) {
                 return;
             }
+            // If the network logging was enabled by the profile owner, then do not
+            // include events in the personal profile.
+            if (!shouldLogNetworkEvent(uid)) {
+                return;
+            }
             DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount,
                     mPm.getNameForUid(uid), timestamp);
             sendNetworkEvent(dnsEvent);
@@ -68,6 +78,11 @@
             if (!mIsLoggingEnabled.get()) {
                 return;
             }
+            // If the network logging was enabled by the profile owner, then do not
+            // include events in the personal profile.
+            if (!shouldLogNetworkEvent(uid)) {
+                return;
+            }
             ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid),
                     timestamp);
             sendNetworkEvent(connectEvent);
@@ -81,11 +96,17 @@
             msg.setData(bundle);
             mNetworkLoggingHandler.sendMessage(msg);
         }
+
+        private boolean shouldLogNetworkEvent(int uid) {
+            return mTargetUserId == UserHandle.USER_ALL
+                    || mTargetUserId == UserHandle.getUserId(uid);
+        }
     };
 
-    NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) {
+    NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm, int targetUserId) {
         mDpm = dpm;
         mPm = pm;
+        mTargetUserId = targetUserId;
     }
 
     private boolean checkIpConnectivityMetricsService() {
@@ -114,7 +135,7 @@
                         /* allowIo */ false);
                 mHandlerThread.start();
                 mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(),
-                        mDpm);
+                        mDpm, mTargetUserId);
                 mNetworkLoggingHandler.scheduleBatchFinalization();
                 mIsLoggingEnabled.set(true);
                 return true;
@@ -153,7 +174,7 @@
     }
 
     /**
-     * If logs are being collected, keep collecting them but stop notifying the device owner that
+     * If logs are being collected, keep collecting them but stop notifying the admin that
      * new logs are available (since they cannot be retrieved)
      */
     void pause() {
@@ -163,11 +184,11 @@
     }
 
     /**
-     * If logs are being collected, start notifying the device owner when logs are ready to be
+     * If logs are being collected, start notifying the admin when logs are ready to be
      * collected again (if it was paused).
      * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
-     * to notify the device owner. Therefore calling identity should be cleared before calling it
-     * (in case the method is called from a user other than the DO's user).
+     * to notify the admin. Therefore calling identity should be cleared before calling it
+     * (in case the method is called from a user other than the admin's user).
      */
     void resume() {
         if (mNetworkLoggingHandler != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 0a7070f..84e89a0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -50,7 +50,7 @@
     private static final int MAX_EVENTS_PER_BATCH = 1200;
 
     /**
-     * Maximum number of batches to store in memory. If more batches are generated and the DO
+     * Maximum number of batches to store in memory. If more batches are generated and the admin
      * doesn't fetch them, we will discard the oldest one.
      */
     private static final int MAX_BATCHES = 5;
@@ -74,6 +74,7 @@
     private final AlarmManager mAlarmManager;
 
     private long mId;
+    private int mTargetUserId;
 
     private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
         @Override
@@ -82,10 +83,10 @@
                     + mNetworkEvents.size() + " pending events.");
             Bundle notificationExtras = null;
             synchronized (NetworkLoggingHandler.this) {
-                notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+                notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
             }
             if (notificationExtras != null) {
-                notifyDeviceOwner(notificationExtras);
+                notifyDeviceOwnerOrProfileOwner(notificationExtras);
             }
         }
     };
@@ -98,8 +99,8 @@
     private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
 
     /**
-     * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
-     * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
+     * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the admin.
+     * Already retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
      */
     @GuardedBy("this")
     private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
@@ -115,16 +116,18 @@
     @GuardedBy("this")
     private long mLastRetrievedBatchToken;
 
-    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
-        this(looper, dpm, 0 /* event id */);
+    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, int targetUserId) {
+        this(looper, dpm, 0 /* event id */, targetUserId);
     }
 
     @VisibleForTesting
-    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) {
+    NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id,
+            int targetUserId) {
         super(looper);
         this.mDpm = dpm;
         this.mAlarmManager = mDpm.mInjector.getAlarmManager();
         this.mId = id;
+        this.mTargetUserId = targetUserId;
     }
 
     @Override
@@ -137,11 +140,11 @@
                     synchronized (NetworkLoggingHandler.this) {
                         mNetworkEvents.add(networkEvent);
                         if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
-                            notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+                            notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
                         }
                     }
                     if (notificationExtras != null) {
-                        notifyDeviceOwner(notificationExtras);
+                        notifyDeviceOwnerOrProfileOwner(notificationExtras);
                     }
                 }
                 break;
@@ -176,10 +179,10 @@
             if (toWaitNanos > 0) {
                 return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
             }
-            notificationExtras = finalizeBatchAndBuildDeviceOwnerMessageLocked();
+            notificationExtras = finalizeBatchAndBuildAdminMessageLocked();
         }
         if (notificationExtras != null) {
-            notifyDeviceOwner(notificationExtras);
+            notifyDeviceOwnerOrProfileOwner(notificationExtras);
         }
         return 0;
     }
@@ -201,14 +204,15 @@
                     + ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
             mPaused = false;
 
-            // If there is a batch ready that the device owner hasn't been notified about, do it now.
+            // If there is a batch ready that the device owner or profile owner hasn't been
+            // notified about, do it now.
             if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
                 scheduleBatchFinalization();
-                notificationExtras = buildDeviceOwnerMessageLocked();
+                notificationExtras = buildAdminMessageLocked();
             }
         }
         if (notificationExtras != null) {
-            notifyDeviceOwner(notificationExtras);
+            notifyDeviceOwnerOrProfileOwner(notificationExtras);
         }
     }
 
@@ -219,8 +223,8 @@
     }
 
     @GuardedBy("this")
-    /** @returns extras if a message should be sent to the device owner */
-    private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
+    /** @return extras if a message should be sent to the device owner or profile owner */
+    private Bundle finalizeBatchAndBuildAdminMessageLocked() {
         mLastFinalizationNanos = System.nanoTime();
         Bundle notificationExtras = null;
         if (mNetworkEvents.size() > 0) {
@@ -243,10 +247,10 @@
             mBatches.append(mCurrentBatchToken, mNetworkEvents);
             mNetworkEvents = new ArrayList<>();
             if (!mPaused) {
-                notificationExtras = buildDeviceOwnerMessageLocked();
+                notificationExtras = buildAdminMessageLocked();
             }
         } else {
-            // Don't notify the DO, since there are no events; DPC can still retrieve
+            // Don't notify the admin, since there are no events; DPC can still retrieve
             // the last full batch if not paused.
             Slog.d(TAG, "Was about to finalize the batch, but there were no events to send to"
                     + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
@@ -257,9 +261,9 @@
     }
 
     @GuardedBy("this")
-    /** Build extras notification to the DO. Should only be called when there
+    /** Build extras notification to the admin. Should only be called when there
         is a batch available. */
-    private Bundle buildDeviceOwnerMessageLocked() {
+    private Bundle buildAdminMessageLocked() {
         final Bundle extras = new Bundle();
         final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
         extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
@@ -267,16 +271,18 @@
         return extras;
     }
 
-    /** Sends a notification to the DO. Should not hold locks as DevicePolicyManagerService may
-        call into NetworkLoggingHandler. */
-    private void notifyDeviceOwner(Bundle extras) {
-        Slog.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
-                + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+    /** Sends a notification to the device owner or profile owner. Should not hold locks as
+        DevicePolicyManagerService may call into NetworkLoggingHandler. */
+    private void notifyDeviceOwnerOrProfileOwner(Bundle extras) {
         if (Thread.holdsLock(this)) {
             Slog.wtfStack(TAG, "Shouldn't be called with NetworkLoggingHandler lock held");
             return;
         }
-        mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+        Slog.d(TAG, "Sending network logging batch broadcast to device owner or profile owner, "
+                + "batchToken: "
+                + extras.getLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, -1));
+        mDpm.sendDeviceOwnerOrProfileOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE,
+                extras, mTargetUserId);
     }
 
     synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index 883f95d..7de1bd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,16 +15,18 @@
  */
 package com.android.server.devicepolicy;
 
-import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
 import static android.app.admin.DevicePolicyManager.operationToString;
-import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
 
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
-import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.util.Slog;
 
 import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 
 import java.util.Objects;
 
@@ -43,10 +45,10 @@
     private final DevicePolicyManagerService mService;
     private final DevicePolicySafetyChecker mRealSafetyChecker;
     private final @DevicePolicyOperation int mOperation;
-    private final @UnsafeOperationReason int mReason;
+    private final @OperationSafetyReason int mReason;
 
     OneTimeSafetyChecker(DevicePolicyManagerService service,
-            @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
+            @DevicePolicyOperation int operation, @OperationSafetyReason int reason) {
         mService = Objects.requireNonNull(service);
         mOperation = operation;
         mReason = reason;
@@ -55,24 +57,42 @@
     }
 
     @Override
-    @UnsafeOperationReason
+    @OperationSafetyReason
     public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
         String name = operationToString(operation);
-        int reason = UNSAFE_OPERATION_REASON_NONE;
+        Slog.i(TAG, "getUnsafeOperationReason(" + name + ")");
+        int reason = OPERATION_SAFETY_REASON_NONE;
         if (operation == mOperation) {
             reason = mReason;
         } else {
             Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
                     + ", should be " + operationToString(mOperation));
         }
-        Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
-                + unsafeOperationReasonToString(reason)
+        String reasonName = operationSafetyReasonToString(reason);
+        DevicePolicyManagerInternal dpmi = LocalServices
+                .getService(DevicePolicyManagerInternal.class);
+
+        Slog.i(TAG, "notifying " + reasonName + " is active");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, true);
+
+        Slog.i(TAG, "notifying " + reasonName + " is inactive");
+        dpmi.notifyUnsafeOperationStateChanged(this, reason, false);
+
+        Slog.i(TAG, "returning " + reasonName
                 + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
         mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
         return reason;
     }
 
     @Override
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        boolean safe = mReason != reason;
+        Slog.i(TAG, "isSafeOperation(" + operationSafetyReasonToString(reason) + "): " + safe);
+
+        return safe;
+    }
+
+    @Override
     public void onFactoryReset(IResultReceiver callback) {
         throw new UnsupportedOperationException();
     }
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 56cb3d1..886c1e5 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -69,6 +69,14 @@
     static constexpr auto progressUpdateInterval = 1000ms;
     static constexpr auto perUidTimeoutOffset = progressUpdateInterval * 2;
     static constexpr auto minPerUidTimeout = progressUpdateInterval * 3;
+
+    // If DL was up and not crashing for 10mins, we consider it healthy and reset all delays.
+    static constexpr auto healthyDataLoaderUptime = 10min;
+    // 10s, 100s (~2min), 1000s (~15min), 10000s (~3hrs)
+    static constexpr auto minBindDelay = 10s;
+    static constexpr auto maxBindDelay = 10000s;
+    static constexpr auto bindDelayMultiplier = 10;
+    static constexpr auto bindDelayJitterDivider = 10;
 };
 
 static const Constants& constants() {
@@ -386,6 +394,28 @@
     dprintf(fd, "}\n");
 }
 
+bool IncrementalService::needStartDataLoaderLocked(IncFsMount& ifs) {
+    if (ifs.dataLoaderStub->params().packageName == Constants::systemPackage) {
+        return true;
+    }
+
+    // Check all permanent binds.
+    for (auto&& [_, bindPoint] : ifs.bindPoints) {
+        if (bindPoint.kind != BindKind::Permanent) {
+            continue;
+        }
+        const auto progress = getLoadingProgressFromPath(ifs, bindPoint.sourceDir,
+                                                         /*stopOnFirstIncomplete=*/true);
+        if (!progress.isError() && !progress.fullyLoaded()) {
+            LOG(INFO) << "Non system mount: [" << bindPoint.sourceDir
+                      << "], partial progress: " << progress.getProgress() * 100 << "%";
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void IncrementalService::onSystemReady() {
     if (mSystemReady.exchange(true)) {
         return;
@@ -396,8 +426,11 @@
         std::lock_guard l(mLock);
         mounts.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id &&
-                ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
+            if (ifs->mountId != id) {
+                continue;
+            }
+
+            if (needStartDataLoaderLocked(*ifs)) {
                 mounts.push_back(ifs);
             }
         }
@@ -1539,6 +1572,11 @@
     return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
 }
 
+template <class Duration>
+static constexpr auto castToMs(Duration d) {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(d);
+}
+
 // Extract lib files from zip, create new files in incfs and write data to them
 // Lib files should be placed next to the APK file in the following matter:
 // Example:
@@ -2134,9 +2172,43 @@
                << status << " (current " << mCurrentStatus << ")";
 }
 
+Milliseconds IncrementalService::DataLoaderStub::updateBindDelay() {
+    std::unique_lock lock(mMutex);
+    const auto previousBindTs = mPreviousBindTs;
+    const auto now = Clock::now();
+    mPreviousBindTs = now;
+
+    const auto nonCrashingInterval = std::max(castToMs(now - previousBindTs), 100ms);
+    if (previousBindTs.time_since_epoch() == Clock::duration::zero() ||
+        nonCrashingInterval > Constants::healthyDataLoaderUptime) {
+        mPreviousBindDelay = 0ms;
+        return mPreviousBindDelay;
+    }
+
+    constexpr auto minBindDelayMs = castToMs(Constants::minBindDelay);
+    constexpr auto maxBindDelayMs = castToMs(Constants::maxBindDelay);
+
+    const auto bindDelayMs =
+            std::min(std::max(mPreviousBindDelay * Constants::bindDelayMultiplier, minBindDelayMs),
+                     maxBindDelayMs)
+                    .count();
+    const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
+    const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+    mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
+
+    return mPreviousBindDelay;
+}
+
 bool IncrementalService::DataLoaderStub::bind() {
+    const auto bindDelay = updateBindDelay();
+    if (bindDelay > 1s) {
+        LOG(INFO) << "Delaying bind to " << mParams.packageName << " by "
+                  << bindDelay.count() / 1000 << "s";
+    }
+
     bool result = false;
-    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
+    auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, bindDelay.count(),
+                                                                this, &result);
     if (!status.isOk() || !result) {
         LOG(ERROR) << "Failed to bind a data loader for mount " << id();
         return false;
@@ -2249,7 +2321,8 @@
 
         listener = mStatusListener;
 
-        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
+        if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE ||
+            mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE) {
             // For unavailable, unbind from DataLoader to ensure proper re-commit.
             setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
         }
@@ -2544,6 +2617,9 @@
         dprintf(fd, "          blockIndex: %d\n", pendingRead.block);
         dprintf(fd, "          bootClockTsUs: %lld\n", (long long)pendingRead.bootClockTsUs);
     }
+    dprintf(fd, "        bind: %llds ago (delay: %llds)\n",
+            (long long)(elapsedMcs(mPreviousBindTs, Clock::now()) / 1000000),
+            (long long)(mPreviousBindDelay.count() / 1000));
     dprintf(fd, "      }\n");
     const auto& params = mParams;
     dprintf(fd, "      dataLoaderParams: {\n");
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 5d53bac..459ed32 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -245,7 +245,6 @@
         void setTargetStatusLocked(int status);
 
         bool fsmStep();
-        bool fsmStep(int currentStatus, int targetStatus);
 
         void onHealthStatus(StorageHealthListener healthListener, int healthStatus);
         void updateHealthStatus(bool baseline = false);
@@ -259,6 +258,8 @@
 
         BootClockTsUs getOldestPendingReadTs();
 
+        Milliseconds updateBindDelay();
+
         void registerForPendingReads();
         void unregisterFromPendingReads();
 
@@ -276,6 +277,9 @@
         int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
         TimePoint mTargetStatusTs = {};
 
+        TimePoint mPreviousBindTs = {};
+        Milliseconds mPreviousBindDelay = {};
+
         std::string mHealthPath;
         incfs::UniqueControl mHealthControl;
         struct {
@@ -370,6 +374,8 @@
     void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
                                   std::string&& source, std::string&& target, BindKind kind);
 
+    bool needStartDataLoaderLocked(IncFsMount& ifs);
+
     DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
                                         content::pm::DataLoaderParamsParcel&& params,
                                         const DataLoaderStatusListener* statusListener = nullptr,
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 25d3f77..659d650 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -70,9 +70,10 @@
     ~RealDataLoaderManager() = default;
     binder::Status bindToDataLoader(MountId mountId,
                                     const content::pm::DataLoaderParamsParcel& params,
+                                    int bindDelayMs,
                                     const sp<content::pm::IDataLoaderStatusListener>& listener,
                                     bool* _aidl_return) const final {
-        return mInterface->bindToDataLoader(mountId, params, listener, _aidl_return);
+        return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
     }
     binder::Status getDataLoader(MountId mountId,
                                  sp<content::pm::IDataLoader>* _aidl_return) const final {
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 71fd3ac..d60035a 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -63,7 +63,7 @@
 public:
     virtual ~DataLoaderManagerWrapper() = default;
     virtual binder::Status bindToDataLoader(
-            MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+            MountId mountId, const content::pm::DataLoaderParamsParcel& params, int bindDelayMs,
             const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
     virtual binder::Status getDataLoader(MountId mountId,
                                          sp<content::pm::IDataLoader>* result) const = 0;
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8713f9d..ab491ef 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -208,8 +208,9 @@
         EXPECT_TRUE(mDataLoaderHolder != nullptr);
     }
 
-    MOCK_CONST_METHOD4(bindToDataLoader,
+    MOCK_CONST_METHOD5(bindToDataLoader,
                        binder::Status(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return));
     MOCK_CONST_METHOD2(getDataLoader,
@@ -217,11 +218,11 @@
     MOCK_CONST_METHOD1(unbindFromDataLoader, binder::Status(int32_t mountId));
 
     void bindToDataLoaderSuccess() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::bindToDataLoaderOk));
     }
     void bindToDataLoaderFails() {
-        ON_CALL(*this, bindToDataLoader(_, _, _, _))
+        ON_CALL(*this, bindToDataLoader(_, _, _, _, _))
                 .WillByDefault(Return(
                         (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
     }
@@ -234,6 +235,7 @@
                 .WillByDefault(Invoke(this, &MockDataLoaderManager::unbindFromDataLoaderOk));
     }
     binder::Status bindToDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
+                                      int bindDelayMs,
                                       const sp<IDataLoaderStatusListener>& listener,
                                       bool* _aidl_return) {
         mId = mountId;
@@ -245,6 +247,40 @@
         }
         return binder::Status::ok();
     }
+    binder::Status bindToDataLoaderOkWith10sDelay(int32_t mountId,
+                                                  const DataLoaderParamsParcel& params,
+                                                  int bindDelayMs,
+                                                  const sp<IDataLoaderStatusListener>& listener,
+                                                  bool* _aidl_return) {
+        CHECK(1000 * 9 <= bindDelayMs && bindDelayMs <= 1000 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith100sDelay(int32_t mountId,
+                                                   const DataLoaderParamsParcel& params,
+                                                   int bindDelayMs,
+                                                   const sp<IDataLoaderStatusListener>& listener,
+                                                   bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith1000sDelay(int32_t mountId,
+                                                    const DataLoaderParamsParcel& params,
+                                                    int bindDelayMs,
+                                                    const sp<IDataLoaderStatusListener>& listener,
+                                                    bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11) << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+    binder::Status bindToDataLoaderOkWith10000sDelay(int32_t mountId,
+                                                     const DataLoaderParamsParcel& params,
+                                                     int bindDelayMs,
+                                                     const sp<IDataLoaderStatusListener>& listener,
+                                                     bool* _aidl_return) {
+        CHECK(1000 * 9 * 9 * 9 * 9 < bindDelayMs && bindDelayMs < 1000 * 11 * 11 * 11 * 11)
+                << bindDelayMs;
+        return bindToDataLoaderOk(mountId, params, bindDelayMs, listener, _aidl_return);
+    }
+
     binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
         *_aidl_return = mDataLoader;
         return binder::Status::ok();
@@ -261,6 +297,9 @@
     void setDataLoaderStatusUnavailable() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
     }
+    void setDataLoaderStatusUnrecoverable() {
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_UNRECOVERABLE);
+    }
     binder::Status unbindFromDataLoaderOk(int32_t id) {
         if (mDataLoader) {
             if (auto status = mDataLoader->destroy(id); !status.isOk()) {
@@ -676,7 +715,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
     mVold->mountIncFsFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
@@ -686,7 +725,7 @@
 
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
     mVold->mountIncFsInvalidControlParcel();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     TemporaryDir tempDir;
     int storageId =
@@ -698,7 +737,7 @@
 TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
     mVold->mountIncFsSuccess();
     mIncFs->makeFileFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -712,7 +751,7 @@
     mVold->mountIncFsSuccess();
     mIncFs->makeFileSuccess();
     mVold->bindMountFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
@@ -727,7 +766,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->bindToDataLoaderFails();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -755,11 +794,11 @@
     mIncrementalService->deleteStorage(storageId);
 }
 
-TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+TEST_F(IncrementalServiceTest, testDataLoaderDestroyedAndDelayed) {
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(6);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
-    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(2);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(6);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(6);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
@@ -768,13 +807,38 @@
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
     mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
+
     // Simulated crash/other connection breakage.
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith100sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith1000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
+    mDataLoaderManager->setDataLoaderStatusDestroyed();
+
+    ON_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _))
+            .WillByDefault(Invoke(mDataLoaderManager,
+                                  &MockDataLoaderManager::bindToDataLoaderOkWith10000sDelay));
     mDataLoaderManager->setDataLoaderStatusDestroyed();
 }
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -793,7 +857,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -811,7 +875,7 @@
 
 TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) {
     mDataLoader->initializeCreateOkNoStatus();
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -827,12 +891,30 @@
     mDataLoaderManager->setDataLoaderStatusUnavailable();
 }
 
+TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnrecoverable) {
+    mDataLoader->initializeCreateOkNoStatus();
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
+    mDataLoaderManager->setDataLoaderStatusUnrecoverable();
+}
+
 TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) {
     mIncFs->waitForPendingReadsSuccess();
     mIncFs->openMountSuccess();
     mDataLoader->initializeCreateOkNoStatus();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
@@ -856,7 +938,7 @@
 TEST_F(IncrementalServiceTest, testStartDataLoaderUnhealthyStorage) {
     mIncFs->openMountSuccess();
 
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
@@ -1406,7 +1488,7 @@
 }
 
 TEST_F(IncrementalServiceTest, testPerUidTimeoutsTooShort) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 1801f3b..a2768c6 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,10 @@
 import android.Manifest;
 import android.content.Context;
 import android.os.ISystemConfig;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -64,6 +68,22 @@
             return SystemConfig.getInstance()
                     .getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
         }
+
+        @Override
+        public int[] getSystemPermissionUids(String permissionName) {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS,
+                    "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS");
+            final List<Integer> uids = new ArrayList<>();
+            final SparseArray<ArraySet<String>> systemPermissions =
+                    SystemConfig.getInstance().getSystemPermissions();
+            for (int i = 0; i < systemPermissions.size(); i++) {
+                final ArraySet<String> permissions = systemPermissions.valueAt(i);
+                if (permissions != null && permissions.contains(permissionName)) {
+                    uids.add(systemPermissions.keyAt(i));
+                }
+            }
+            return ArrayUtils.convertToIntArray(uids);
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 98c3b99..bd20464 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -195,6 +195,7 @@
 import com.android.server.uri.UriGrantsManagerService;
 import com.android.server.usage.UsageStatsService;
 import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.vibrator.VibratorManagerService;
 import com.android.server.vr.VrManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.ActivityTaskManagerService;
@@ -525,8 +526,7 @@
             String filename = "/data/system/heapdump/fdtrack-" + date + ".hprof";
             Debug.dumpHprofData(filename);
         } catch (IOException ex) {
-            Slog.e("System", "Failed to dump fdtrack hprof");
-            ex.printStackTrace();
+            Slog.e("System", "Failed to dump fdtrack hprof", ex);
         }
     }
 
@@ -1292,11 +1292,11 @@
         t.traceBegin("startOtherServices");
 
         final Context context = mSystemContext;
-        VibratorService vibrator = null;
         DynamicSystemService dynamicSystem = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
         IpSecService ipSecService = null;
+        VpnManagerService vpnManager = null;
         VcnManagementService vcnManagement = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
@@ -1418,11 +1418,6 @@
             mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
             t.traceEnd();
 
-            t.traceBegin("StartVibratorService");
-            vibrator = new VibratorService(context);
-            ServiceManager.addService("vibrator", vibrator);
-            t.traceEnd();
-
             t.traceBegin("StartDynamicSystemService");
             dynamicSystem = new DynamicSystemService(context);
             ServiceManager.addService("dynamic_system", dynamicSystem);
@@ -1890,6 +1885,15 @@
             networkPolicy.bindConnectivityManager(connectivity);
             t.traceEnd();
 
+            t.traceBegin("StartVpnManagerService");
+            try {
+                vpnManager = VpnManagerService.create(context);
+                ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager);
+            } catch (Throwable e) {
+                reportWtf("starting VPN Manager Service", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartVcnManagementService");
             try {
                 vcnManagement = VcnManagementService.create(context);
@@ -2491,14 +2495,6 @@
 
         // It is now time to start up the app processes...
 
-        t.traceBegin("MakeVibratorServiceReady");
-        try {
-            vibrator.systemReady();
-        } catch (Throwable e) {
-            reportWtf("making Vibrator Service ready", e);
-        }
-        t.traceEnd();
-
         t.traceBegin("MakeLockSettingsServiceReady");
         if (lockSettings != null) {
             try {
@@ -2626,6 +2622,7 @@
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
         final IpSecService ipSecServiceF = ipSecService;
+        final VpnManagerService vpnManagerF = vpnManager;
         final VcnManagementService vcnManagementF = vcnManagement;
         final WindowManagerService windowManagerF = wm;
         final ConnectivityManager connectivityF = (ConnectivityManager)
@@ -2740,6 +2737,15 @@
                 reportWtf("making Connectivity Service ready", e);
             }
             t.traceEnd();
+            t.traceBegin("MakeVpnManagerServiceReady");
+            try {
+                if (vpnManagerF != null) {
+                    vpnManagerF.systemReady();
+                }
+            } catch (Throwable e) {
+                reportWtf("making VpnManagerService ready", e);
+            }
+            t.traceEnd();
             t.traceBegin("MakeVcnManagementServiceReady");
             try {
                 if (vcnManagementF != null) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d863194..2d23fb4 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.content.pm.PackageUserState
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.content.pm.parsing.component.ParsedActivity
@@ -25,7 +26,6 @@
 import android.os.Build
 import android.os.Process
 import android.util.ArraySet
-import android.util.Singleton
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.pm.PackageSetting
@@ -46,14 +46,11 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verifyNoMoreInteractions
-import org.testng.Assert.assertThrows
 import java.io.File
 import java.util.UUID
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
 
-private typealias Enforcer = DomainVerificationEnforcer
-
 @RunWith(Parameterized::class)
 class DomainVerificationEnforcerTest {
 
@@ -64,64 +61,27 @@
         private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1
         private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2
 
-        private const val TEST_PKG = "com.test"
+        private const val VISIBLE_PKG = "com.test.visible"
+        private val VISIBLE_UUID = UUID.fromString("8db01272-270d-4606-a3db-bb35228ff9a2")
+        private const val INVISIBLE_PKG = "com.test.invisible"
+        private val INVISIBLE_UUID = UUID.fromString("16dcb029-d96c-4a19-833a-4c9d72e2ebc3")
 
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
         fun parameters(): Array<Any> {
+            val visiblePkg = mockPkg(VISIBLE_PKG)
+            val visiblePkgSetting = mockPkgSetting(VISIBLE_PKG, VISIBLE_UUID)
+            val invisiblePkg = mockPkg(INVISIBLE_PKG)
+            val invisiblePkgSetting = mockPkgSetting(INVISIBLE_PKG, INVISIBLE_UUID)
+
             val makeEnforcer: (Context) -> DomainVerificationEnforcer = {
-                DomainVerificationEnforcer(it)
-            }
-
-            val mockPkg = mockThrowOnUnmocked<AndroidPackage> {
-                whenever(packageName) { TEST_PKG }
-                whenever(targetSdkVersion) { Build.VERSION_CODES.S }
-                whenever(activities) {
-                    listOf(
-                        ParsedActivity().apply {
-                            addIntent(
-                                ParsedIntentInfo().apply {
-                                    autoVerify = true
-                                    addAction(Intent.ACTION_VIEW)
-                                    addCategory(Intent.CATEGORY_BROWSABLE)
-                                    addCategory(Intent.CATEGORY_DEFAULT)
-                                    addDataScheme("https")
-                                    addDataAuthority("example.com", null)
-                                }
-                            )
+                DomainVerificationEnforcer(it).apply {
+                    setCallback(mockThrowOnUnmocked {
+                        whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+                        whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+                            true
                         }
-                    )
-                }
-            }
-
-            val uuid = UUID.randomUUID()
-
-            // TODO: PackageSetting field encapsulation to move to whenever(name)
-            val mockPkgSetting = spyThrowOnUnmocked(
-                PackageSetting(
-                    TEST_PKG,
-                    TEST_PKG,
-                    File("/test"),
-                    null,
-                    null,
-                    null,
-                    null,
-                    1,
-                    0,
-                    0,
-                    0,
-                    null,
-                    null,
-                    null,
-                    uuid
-                )
-            ) {
-                whenever(getPkg()) { mockPkg }
-                whenever(domainSetId) { uuid }
-                whenever(userState) {
-                    SparseArray<PackageUserState>().apply {
-                        this[0] = PackageUserState()
-                    }
+                    })
                 }
             }
 
@@ -129,142 +89,160 @@
                 {
                     val callingUidInt = AtomicInteger(-1)
                     val callingUserIdInt = AtomicInteger(-1)
-                    Triple(
-                        callingUidInt, callingUserIdInt, DomainVerificationService(
-                            it,
-                            mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
-                            mockThrowOnUnmocked {
-                                whenever(
-                                    isChangeEnabled(
-                                        anyLong(),
-                                        any()
-                                    )
-                                ) { true }
-                            }).apply {
-                                setConnection(mockThrowOnUnmocked {
-                                    whenever(callingUid) { callingUidInt.get() }
-                                    whenever(callingUserId) { callingUserIdInt.get() }
-                                    whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting }
-                                    whenever(getPackageLocked(TEST_PKG)) { mockPkg }
-                                    whenever(schedule(anyInt(), any()))
-                                    whenever(scheduleWriteSettings())
-                                })
+
+                    val connection: DomainVerificationManagerInternal.Connection =
+                        mockThrowOnUnmocked {
+                            whenever(callingUid) { callingUidInt.get() }
+                            whenever(callingUserId) { callingUserIdInt.get() }
+                            whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
+                            whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
+                            whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
+                            whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
+                            whenever(schedule(anyInt(), any()))
+                            whenever(scheduleWriteSettings())
+                            whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
+                            whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
+                                true
+                            }
                         }
-                    )
+                    val service = DomainVerificationService(
+                        it,
+                        mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+                        mockThrowOnUnmocked {
+                            whenever(
+                                isChangeEnabled(
+                                    anyLong(),
+                                    any()
+                                )
+                            ) { true }
+                        }).apply {
+                        setConnection(connection)
+                    }
+
+                    Triple(callingUidInt, callingUserIdInt, service)
                 }
 
             fun enforcer(
                 type: Type,
                 name: String,
-                block: DomainVerificationEnforcer.(
-                    callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
-                ) -> Unit
-            ) = Params(
-                type,
-                makeEnforcer,
-                name
-            ) { enforcer, callingUid, callingUserId, userId, proxy ->
-                enforcer.block(callingUid, callingUserId, userId, proxy)
+                block: DomainVerificationEnforcer.(Params.Input<DomainVerificationEnforcer>) -> Any?
+            ) = Params(type, makeEnforcer, name) {
+                it.target.block(it)
             }
 
             fun service(
                 type: Type,
                 name: String,
-                block: DomainVerificationService.(
-                    callingUid: Int, callingUserId: Int, userId: Int
-                ) -> Unit
-            ) = Params(
-                type,
-                makeService,
-                name
-            ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy ->
-                val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService
-                callingUidInt.set(callingUid)
-                callingUserIdInt.set(callingUserId)
-                service.setProxy(proxy)
-                service.addPackage(mockPkgSetting)
-                service.block(callingUid, callingUserId, userId)
+                block: DomainVerificationService.(Params.Input<Triple<AtomicInteger, AtomicInteger, DomainVerificationService>>) -> Any?
+            ) = Params(type, makeService, name) {
+                val (callingUidInt, callingUserIdInt, service) = it.target
+                callingUidInt.set(it.callingUid)
+                callingUserIdInt.set(it.callingUserId)
+                service.proxy = it.proxy
+                service.addPackage(visiblePkgSetting)
+                service.addPackage(invisiblePkgSetting)
+                service.block(it)
             }
 
             return arrayOf(
-                enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
-                    assertInternal(callingUid)
+                enforcer(Type.INTERNAL, "internal") {
+                    assertInternal(it.callingUid)
                 },
-                enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
-                    assertApprovedQuerent(callingUid, proxy)
+                enforcer(Type.QUERENT, "approvedQuerent") {
+                    assertApprovedQuerent(it.callingUid, it.proxy)
                 },
-                enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
-                    assertApprovedVerifier(callingUid, proxy)
+                enforcer(Type.VERIFIER, "approvedVerifier") {
+                    assertApprovedVerifier(it.callingUid, it.proxy)
                 },
                 enforcer(
                     Type.SELECTOR,
                     "approvedUserSelector"
-                ) { callingUid, callingUserId, userId, _ ->
-                    assertApprovedUserSelector(callingUid, callingUserId, userId)
+                ) {
+                    assertApprovedUserSelector(
+                        it.callingUid, it.callingUserId,
+                        it.targetPackageName, it.userId
+                    )
                 },
-
-                service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+                service(Type.INTERNAL, "setStatusInternalPackageName") {
                     setDomainVerificationStatusInternal(
-                        TEST_PKG,
+                        it.targetPackageName,
                         DomainVerificationManager.STATE_SUCCESS,
                         ArraySet(setOf("example.com"))
                     )
                 },
-                service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+                service(Type.INTERNAL, "setUserSelectionInternal") {
                     setDomainVerificationUserSelectionInternal(
-                        userId,
-                        TEST_PKG,
+                        it.userId,
+                        it.targetPackageName,
                         false,
                         ArraySet(setOf("example.com"))
                     )
                 },
-                service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
-                    verifyPackages(listOf(TEST_PKG), true)
+                service(Type.INTERNAL, "verifyPackages") {
+                    verifyPackages(listOf(it.targetPackageName), true)
                 },
-                service(Type.INTERNAL, "clearState") { _, _, _ ->
-                    clearDomainVerificationState(listOf(TEST_PKG))
+                service(Type.INTERNAL, "clearState") {
+                    clearDomainVerificationState(listOf(it.targetPackageName))
                 },
-                service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
-                    clearUserSelections(listOf(TEST_PKG), userId)
+                service(Type.INTERNAL, "clearUserSelections") {
+                    clearUserSelections(listOf(it.targetPackageName), it.userId)
                 },
-                service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+                service(Type.VERIFIER, "getPackageNames") {
                     validVerificationPackageNames
                 },
-                service(Type.QUERENT, "getInfo") { _, _, _ ->
-                    getDomainVerificationInfo(TEST_PKG)
+                service(Type.QUERENT, "getInfo") {
+                    getDomainVerificationInfo(it.targetPackageName)
                 },
-                service(Type.VERIFIER, "setStatus") { _, _, _ ->
+                service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
-                        uuid,
+                        it.targetDomainSetId,
                         setOf("example.com"),
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+                service(Type.VERIFIER, "setStatusInternalUid") {
                     setDomainVerificationStatusInternal(
-                        callingUid,
-                        uuid,
+                        it.callingUid,
+                        it.targetDomainSetId,
                         setOf("example.com"),
                         DomainVerificationManager.STATE_SUCCESS
                     )
                 },
-                service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
-                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+                service(Type.SELECTOR, "setLinkHandlingAllowed") {
+                    setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true)
                 },
-                service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
-                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+                service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
+                    setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId)
                 },
-                service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
-                    getDomainVerificationUserSelection(TEST_PKG)
+                service(Type.SELECTOR, "getUserSelection") {
+                    getDomainVerificationUserSelection(it.targetPackageName)
                 },
-                service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
-                    getDomainVerificationUserSelection(TEST_PKG, userId)
+                service(Type.SELECTOR_USER, "getUserSelectionUserId") {
+                    getDomainVerificationUserSelection(it.targetPackageName, it.userId)
                 },
-                service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
-                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+                service(Type.SELECTOR, "setUserSelection") {
+                    setDomainVerificationUserSelection(
+                        it.targetDomainSetId,
+                        setOf("example.com"),
+                        true
+                    )
                 },
-                service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
-                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+                service(Type.SELECTOR_USER, "setUserSelectionUserId") {
+                    setDomainVerificationUserSelection(
+                        it.targetDomainSetId,
+                        setOf("example.com"),
+                        true,
+                        it.userId
+                    )
+                },
+                service(Type.LEGACY_SELECTOR, "setLegacyUserState") {
+                    setLegacyUserState(
+                        it.targetPackageName, it.userId,
+                        PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+                    )
+                },
+                service(Type.LEGACY_QUERENT, "getLegacyUserState") {
+                    getLegacyState(it.targetPackageName, it.userId)
                 },
             )
         }
@@ -273,9 +251,7 @@
             val type: Type,
             val construct: (context: Context) -> T,
             val name: String,
-            private val method: (
-                T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
-            ) -> Unit
+            private val method: (Input<T>) -> Any?
         ) {
             override fun toString() = "${type}_$name"
 
@@ -284,12 +260,82 @@
                 callingUid: Int,
                 callingUserId: Int,
                 userId: Int,
+                targetPackageName: String,
+                targetDomainSetId: UUID,
                 proxy: DomainVerificationProxy
-            ) {
-                @Suppress("UNCHECKED_CAST")
-                method(target as T, callingUid, callingUserId, userId, proxy)
+            ): Any? = method(
+                Input(
+                    @Suppress("UNCHECKED_CAST")
+                    target as T,
+                    callingUid,
+                    callingUserId,
+                    userId,
+                    targetPackageName,
+                    targetDomainSetId,
+                    proxy
+                )
+            )
+
+            data class Input<T>(
+                val target: T,
+                val callingUid: Int,
+                val callingUserId: Int,
+                val userId: Int,
+                val targetPackageName: String,
+                val targetDomainSetId: UUID,
+                val proxy: DomainVerificationProxy
+            )
+        }
+
+        fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> {
+            whenever(this.packageName) { packageName }
+            whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+            whenever(activities) {
+                listOf(
+                    ParsedActivity().apply {
+                        addIntent(
+                            ParsedIntentInfo().apply {
+                                autoVerify = true
+                                addAction(Intent.ACTION_VIEW)
+                                addCategory(Intent.CATEGORY_BROWSABLE)
+                                addCategory(Intent.CATEGORY_DEFAULT)
+                                addDataScheme("https")
+                                addDataAuthority("example.com", null)
+                            }
+                        )
+                    }
+                )
             }
         }
+
+        fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked(
+            PackageSetting(
+                packageName,
+                packageName,
+                File("/test"),
+                null,
+                null,
+                null,
+                null,
+                1,
+                0,
+                0,
+                0,
+                null,
+                null,
+                null,
+                domainSetId
+            )
+        ) {
+            whenever(getPkg()) { mockPkg(packageName) }
+            whenever(this.domainSetId) { domainSetId }
+            whenever(userState) {
+                SparseArray<PackageUserState>().apply {
+                    this[0] = PackageUserState()
+                }
+            }
+            whenever(getInstantApp(anyInt())) { false }
+        }
     }
 
     @Parameterized.Parameter(0)
@@ -309,6 +355,8 @@
             Type.VERIFIER -> approvedVerifier()
             Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
             Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+            Type.LEGACY_QUERENT -> legacyQuerent()
+            Type.LEGACY_SELECTOR -> legacyUserSelector()
         }.run { /*exhaust*/ }
     }
 
@@ -316,24 +364,30 @@
         val context: Context = mockThrowOnUnmocked()
         val target = params.construct(context)
 
-        INTERNAL_UIDS.forEach { runMethod(target, it) }
-        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        // Internal doesn't care about visibility
+        listOf(true, false).forEach { visible ->
+            INTERNAL_UIDS.forEach { runMethod(target, it, visible) }
+            assertFails { runMethod(target, VERIFIER_UID, visible) }
+            assertFails {
+                runMethod(target, NON_VERIFIER_UID, visible)
+            }
+        }
     }
 
     fun approvedQuerent() {
         val allowUserSelection = AtomicBoolean(false)
+        val allowPreferredApps = AtomicBoolean(false)
+        val allowQueryAll = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowUserSelection.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowUserSelection,
+                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+            )
+            initPermission(
+                allowPreferredApps,
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+            )
+            initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
         }
         val target = params.construct(context)
 
@@ -341,27 +395,41 @@
 
         verifyNoMoreInteractions(context)
 
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        // Check that the verifier only needs QUERY_ALL to pass
+        allowQueryAll.set(true)
         runMethod(target, VERIFIER_UID)
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        allowQueryAll.set(false)
+
+        allowPreferredApps.set(true)
+
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
 
         allowUserSelection.set(true)
 
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowQueryAll.set(true)
+
         runMethod(target, NON_VERIFIER_UID)
     }
 
     fun approvedVerifier() {
-        val shouldThrow = AtomicBoolean(false)
+        val allowDomainVerificationAgent = AtomicBoolean(false)
+        val allowIntentVerificationAgent = AtomicBoolean(false)
+        val allowQueryAll = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (shouldThrow.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowDomainVerificationAgent,
+                android.Manifest.permission.DOMAIN_VERIFICATION_AGENT
+            )
+            initPermission(
+                allowIntentVerificationAgent,
+                android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT
+            )
+            initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
         }
         val target = params.construct(context)
 
@@ -369,94 +437,241 @@
 
         verifyNoMoreInteractions(context)
 
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowDomainVerificationAgent.set(true)
+
+        assertFails { runMethod(target, VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowQueryAll.set(true)
+
         runMethod(target, VERIFIER_UID)
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
 
-        shouldThrow.set(true)
+        // Check that v1 verifiers are also allowed through
+        allowDomainVerificationAgent.set(false)
+        allowIntentVerificationAgent.set(true)
 
-        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
-        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+        runMethod(target, VERIFIER_UID)
+        assertFails { runMethod(target, NON_VERIFIER_UID) }
     }
 
     fun approvedUserSelector(verifyCrossUser: Boolean) {
-        val allowUserSelection = AtomicBoolean(true)
-        val allowInteractAcrossUsers = AtomicBoolean(true)
+        val allowUserSelection = AtomicBoolean(false)
+        val allowInteractAcrossUsers = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowUserSelection.get()) {
-                    throw SecurityException()
-                }
-            }
-            whenever(
-                enforcePermission(
-                    eq(android.Manifest.permission.INTERACT_ACROSS_USERS),
-                    anyInt(), anyInt(), anyString()
-                )
-            ) {
-                if (!allowInteractAcrossUsers.get()) {
-                    throw SecurityException()
-                }
-            }
+            initPermission(
+                allowUserSelection,
+                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+            )
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
         }
         val target = params.construct(context)
 
-        fun runEachTestCaseWrapped(
-            callingUserId: Int,
-            targetUserId: Int,
-            block: (testCase: () -> Unit) -> Unit = { it.invoke() }
-        ) {
-            block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) }
-            block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) }
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // User selector makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // User selector doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
         }
 
         val callingUserId = 0
         val notCallingUserId = 1
 
-        runEachTestCaseWrapped(callingUserId, callingUserId)
+        runTestCases(callingUserId, callingUserId, throws = true)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId)
+            runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
-        allowInteractAcrossUsers.set(false)
+        allowUserSelection.set(true)
 
-        runEachTestCaseWrapped(callingUserId, callingUserId)
-
+        runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
-            }
-        }
-
-        allowUserSelection.set(false)
-
-        runEachTestCaseWrapped(callingUserId, callingUserId) {
-            assertThrows(SecurityException::class.java, it)
-        }
-        if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
-            }
+            runTestCases(callingUserId, notCallingUserId, throws = true)
         }
 
         allowInteractAcrossUsers.set(true)
 
-        runEachTestCaseWrapped(callingUserId, callingUserId) {
-            assertThrows(SecurityException::class.java, it)
-        }
+        runTestCases(callingUserId, callingUserId, throws = false)
         if (verifyCrossUser) {
-            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
-                assertThrows(SecurityException::class.java, it)
+            runTestCases(callingUserId, notCallingUserId, throws = false)
+        }
+    }
+
+    private fun legacyUserSelector() {
+        val allowInteractAcrossUsers = AtomicBoolean(false)
+        val allowPreferredApps = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
+            initPermission(
+                allowPreferredApps,
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS
+            )
+        }
+        val target = params.construct(context)
+
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // Legacy makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runTestCases(callingUserId, callingUserId, throws = true)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowPreferredApps.set(true)
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowInteractAcrossUsers.set(true)
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = false)
+    }
+
+    private fun legacyQuerent() {
+        val allowInteractAcrossUsers = AtomicBoolean(false)
+        val allowInteractAcrossUsersFull = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            initPermission(
+                allowInteractAcrossUsers,
+                android.Manifest.permission.INTERACT_ACROSS_USERS
+            )
+            initPermission(
+                allowInteractAcrossUsersFull,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+            )
+        }
+        val target = params.construct(context)
+
+        fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+            // Legacy makes no distinction by UID
+            val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+            if (throws) {
+                allUids.forEach {
+                    assertFails {
+                        runMethod(target, it, visible = true, callingUserId, targetUserId)
+                    }
+                }
+            } else {
+                allUids.forEach {
+                    runMethod(target, it, visible = true, callingUserId, targetUserId)
+                }
+            }
+
+            // Legacy doesn't use QUERY_ALL, so the invisible package should always fail
+            allUids.forEach {
+                assertFails {
+                    runMethod(target, it, visible = false, callingUserId, targetUserId)
+                }
+            }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        // Legacy requires the _FULL permission, so this should continue to fail
+        allowInteractAcrossUsers.set(true)
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = true)
+
+        allowInteractAcrossUsersFull.set(true)
+        runTestCases(callingUserId, callingUserId, throws = false)
+        runTestCases(callingUserId, notCallingUserId, throws = false)
+    }
+
+    private fun Context.initPermission(boolean: AtomicBoolean, permission: String) {
+        whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) {
+            if (!boolean.get()) {
+                throw SecurityException()
+            }
+        }
+        whenever(checkPermission(eq(permission), anyInt(), anyInt())) {
+            if (boolean.get()) {
+                PackageManager.PERMISSION_GRANTED
+            } else {
+                PackageManager.PERMISSION_DENIED
             }
         }
     }
 
-    private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
-        params.runMethod(target, callingUid, callingUserId, userId, proxy)
+    private fun runMethod(
+        target: Any,
+        callingUid: Int,
+        visible: Boolean = true,
+        callingUserId: Int = 0,
+        userId: Int = 0
+    ): Any? {
+        val packageName = if (visible) VISIBLE_PKG else INVISIBLE_PKG
+        val uuid = if (visible) VISIBLE_UUID else INVISIBLE_UUID
+        return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy)
+    }
+
+    private fun assertFails(block: () -> Any?) {
+        try {
+            val value = block()
+            // Some methods return false rather than throwing, so check that as well
+            if ((value as? Boolean) != false) {
+                // Can also return default value if it's a legacy call
+                if ((value as? Int)
+                    != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
+                ) {
+                    throw AssertionError("Expected call to return false, was $value")
+                }
+            }
+        } catch (e: SecurityException) {
+        } catch (e: PackageManager.NameNotFoundException) {
+        } catch (e: DomainVerificationManager.InvalidDomainSetException) {
+            // Any of these 3 exceptions are considered failures, which is expected
+        }
     }
 
     enum class Type {
@@ -473,6 +688,12 @@
         SELECTOR,
 
         // Holding the user setting permission, but targeting cross user
-        SELECTOR_USER
+        SELECTOR_USER,
+
+        // Legacy required no permissions except when cross-user
+        LEGACY_QUERENT,
+
+        // Holding the legacy preferred apps permission
+        LEGACY_SELECTOR
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
index a76152c..a92ab9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -104,14 +104,14 @@
 
             userSelectionStates[1] = DomainVerificationUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
-                isDisallowLinkHandling = false
+                isLinkHandlingAllowed = true
             }
         }
         val stateOne = mockEmptyPkgState(1).apply {
             // It's valid to have a user selection without any autoVerify domains
             userSelectionStates[1] = DomainVerificationUserState(1).apply {
                 addHosts(setOf("example-user1.com", "example-user1.org"))
-                isDisallowLinkHandling = true
+                isLinkHandlingAllowed = false
             }
         }
 
@@ -137,13 +137,15 @@
                         hasAutoVerifyDomains="true"
                         >
                         <state>
-                            <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
-                            <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+                            <domain name="example.com" state="${
+                                DomainVerificationManager.STATE_SUCCESS}"/>
+                            <domain name="example.org" state="${
+                                DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
                             <not-domain name="not-domain.com" state="1"/>
                             <domain name="missing-state.com"/>
                         </state>
                         <user-states>
-                            <user-state userId="1" disallowLinkHandling="false">
+                            <user-state userId="1" allowLinkHandling="true">
                                 <enabled-hosts>
                                     <host name="example-user1.com"/>
                                     <not-host name="not-host.com"/>
@@ -171,7 +173,7 @@
                         >
                         <state/>
                         <user-states>
-                            <user-state userId="1" disallowLinkHandling="true">
+                            <user-state userId="1" allowLinkHandling="false">
                                 <enabled-hosts>
                                     <host name="example-user1.com"/>
                                     <host name="example-user1.org"/>
@@ -214,9 +216,9 @@
         stateMap["$packageName.com"] = id
         userSelectionStates[id] = DomainVerificationUserState(id).apply {
             addHosts(setOf("$packageName-user.com"))
-            isDisallowLinkHandling = true
+            isLinkHandlingAllowed = true
         }
     }
 
-    private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+    private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 5792e02..48518f46 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -244,6 +244,7 @@
                     this[0] = PackageUserState()
                 }
             }
+            whenever(getInstantApp(anyInt())) { false }
         }
     }
 
@@ -267,5 +268,8 @@
             whenever(getPackageLocked(TEST_PKG)) { mockPkg() }
             whenever(schedule(anyInt(), any()))
             whenever(scheduleWriteSettings())
+
+            // This doesn't check for visibility; that's done in the enforcer test
+            whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
         }
 }
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 7c935d5..e7d56a0 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -11,9 +11,16 @@
 // 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.
+java_defaults {
+    name: "FrameworkMockingServicesTests-jni-defaults",
+    jni_libs: [
+        "libactivitymanagermockingservicestestjni",
+    ],
+}
 
 android_test {
     name: "FrameworksMockingServicesTests",
+    defaults: ["FrameworkMockingServicesTests-jni-defaults"],
 
     srcs: ["src/**/*.java", "src/**/*.kt"],
 
@@ -72,4 +79,4 @@
     libs: [
         "android.test.runner",
     ],
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
new file mode 100644
index 0000000..928065a
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -0,0 +1,33 @@
+cc_library_shared {
+    name: "libactivitymanagermockingservicestestjni",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        ":lib_cachedAppOptimizer_native",
+        "onload.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/base/libs",
+        "frameworks/native/services",
+        "system/memory/libmeminfo/include",
+    ],
+
+    shared_libs: [
+        "libandroid",
+        "libandroid_runtime",
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libmeminfo",
+        "libnativehelper",
+        "libprocessgroup",
+        "libutils",
+    ],
+}
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
new file mode 100644
index 0000000..147cc47
--- /dev/null
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/*
+ * this is a mini native libaray for cached app optimizer tests to run properly. It
+ * loads all the native methods necessary.
+ */
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+    register_android_server_am_CachedAppOptimizer(env);
+    return JNI_VERSION_1_4;
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index d860326..56d30cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -97,6 +97,7 @@
 
     @Before
     public void setUp() {
+        System.loadLibrary("activitymanagermockingservicestestjni");
         mHandlerThread = new HandlerThread("");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
new file mode 100644
index 0000000..d2ab646
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceIdleHelper.java
@@ -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 com.android.server.location.injector;
+
+public class FakeDeviceIdleHelper extends DeviceIdleHelper {
+
+    private boolean mDeviceIdle = false;
+
+    public void setIdle(boolean deviceIdle) {
+        mDeviceIdle = deviceIdle;
+        notifyDeviceIdleChanged();
+    }
+
+    @Override
+    protected void registerInternal() {}
+
+    @Override
+    protected void unregisterInternal() {}
+
+    @Override
+    public boolean isDeviceIdle() {
+        return mDeviceIdle;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
new file mode 100644
index 0000000..b1d921a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeDeviceStationaryHelper.java
@@ -0,0 +1,52 @@
+/*
+ * 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.android.server.location.injector;
+
+import com.android.server.DeviceIdleInternal;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class FakeDeviceStationaryHelper extends DeviceStationaryHelper {
+
+    private final CopyOnWriteArrayList<DeviceIdleInternal.StationaryListener> mListeners =
+            new CopyOnWriteArrayList<>();
+
+    private boolean mStationary = false;
+
+    @Override
+    public void addListener(DeviceIdleInternal.StationaryListener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+            listener.onDeviceStationaryChanged(mStationary);
+        }
+    }
+
+    @Override
+    public void removeListener(DeviceIdleInternal.StationaryListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public void setStationary(boolean stationary) {
+        synchronized (mListeners) {
+            mStationary = stationary;
+        }
+
+        for (DeviceIdleInternal.StationaryListener listener : mListeners) {
+            listener.onDeviceStationaryChanged(stationary);
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
index 0311920..0597443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java
@@ -19,6 +19,8 @@
 import android.os.IPowerManager;
 import android.os.PowerManager.LocationPowerSaveMode;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 /**
  * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no
  * change".
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
index c39a77e..6156ba9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java
@@ -43,6 +43,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
 
 import org.junit.After;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 8e5b16e..1f102ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -16,9 +16,10 @@
 
 package com.android.server.location.injector;
 
+import com.android.server.location.eventlog.LocationEventLog;
+
 public class TestInjector implements Injector {
 
-    private final LocationEventLog mLocationEventLog;
     private final FakeUserInfoHelper mUserInfoHelper;
     private final FakeAlarmHelper mAlarmHelper;
     private final FakeAppOpsHelper mAppOpsHelper;
@@ -27,20 +28,27 @@
     private final FakeAppForegroundHelper mAppForegroundHelper;
     private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
     private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
+    private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
+    private final FakeDeviceIdleHelper mDeviceIdleHelper;
     private final LocationAttributionHelper mLocationAttributionHelper;
     private final FakeEmergencyHelper mEmergencyHelper;
     private final LocationUsageLogger mLocationUsageLogger;
 
     public TestInjector() {
-        mLocationEventLog = new LocationEventLog();
+        this(new LocationEventLog());
+    }
+
+    public TestInjector(LocationEventLog eventLog) {
         mUserInfoHelper = new FakeUserInfoHelper();
         mAlarmHelper = new FakeAlarmHelper();
         mAppOpsHelper = new FakeAppOpsHelper();
         mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
         mSettingsHelper = new FakeSettingsHelper();
         mAppForegroundHelper = new FakeAppForegroundHelper();
-        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(mLocationEventLog);
+        mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog);
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
+        mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
+        mDeviceIdleHelper = new FakeDeviceIdleHelper();
         mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
@@ -87,6 +95,16 @@
     }
 
     @Override
+    public FakeDeviceStationaryHelper getDeviceStationaryHelper() {
+        return mDeviceStationaryHelper;
+    }
+
+    @Override
+    public FakeDeviceIdleHelper getDeviceIdleHelper() {
+        return mDeviceIdleHelper;
+    }
+
+    @Override
     public LocationAttributionHelper getLocationAttributionHelper() {
         return mLocationAttributionHelper;
     }
@@ -100,9 +118,4 @@
     public LocationUsageLogger getLocationUsageLogger() {
         return mLocationUsageLogger;
     }
-
-    @Override
-    public LocationEventLog getLocationEventLog() {
-        return mLocationEventLog;
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 54fa89a..66b037d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -83,6 +83,7 @@
 
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.location.eventlog.LocationEventLog;
 import com.android.server.location.injector.FakeUserInfoHelper;
 import com.android.server.location.injector.TestInjector;
 
@@ -159,17 +160,19 @@
         doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
         doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
 
-        mInjector = new TestInjector();
+        LocationEventLog eventLog = new LocationEventLog();
+
+        mInjector = new TestInjector(eventLog);
         mInjector.getUserInfoHelper().startUser(OTHER_USER);
 
-        mPassive = new PassiveLocationProviderManager(mContext, mInjector);
+        mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog);
         mPassive.startManager();
         mPassive.setRealProvider(new PassiveLocationProvider(mContext));
 
         mProvider = new TestProvider(PROPERTIES, IDENTITY);
         mProvider.setProviderAllowed(true);
 
-        mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
+        mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive);
         mManager.startManager();
         mManager.setRealProvider(mProvider);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
new file mode 100644
index 0000000..c3cca64
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.android.server.location.provider;
+
+import static com.android.server.location.LocationUtils.createLocation;
+import static com.android.server.location.LocationUtils.createLocationResult;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.location.Location;
+import android.location.LocationResult;
+import android.location.provider.ProviderRequest;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.injector.TestInjector;
+import com.android.server.location.test.FakeProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Random;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StationaryThrottlingLocationProviderTest {
+
+    private static final String TAG = "StationaryThrottlingLocationProviderTest";
+
+    private Random mRandom;
+    private TestInjector mInjector;
+    private FakeProvider mDelegateProvider;
+
+    private @Mock AbstractLocationProvider.Listener mListener;
+    private @Mock FakeProvider.FakeProviderInterface mDelegate;
+
+    private StationaryThrottlingLocationProvider mProvider;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+
+        mInjector = new TestInjector();
+        mDelegateProvider = new FakeProvider(mDelegate);
+
+        mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,
+                mDelegateProvider, new LocationEventLog());
+        mProvider.getController().setListener(mListener);
+        mProvider.getController().start();
+    }
+
+    @After
+    public void tearDown() {
+        mProvider.getController().setRequest(ProviderRequest.EMPTY_REQUEST);
+        mProvider.getController().stop();
+    }
+
+    @Test
+    public void testThrottle() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(false);
+        verify(mDelegate, times(2)).onSetRequest(request);
+        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testThrottle_NoInitialLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+        verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(false);
+        verify(mDelegate, times(2)).onSetRequest(request);
+        verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_noLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+        verify(mListener, never()).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(0)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_oldLocation() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        Location l = createLocation("test_provider", mRandom);
+        l.setElapsedRealtimeNanos(0);
+
+        LocationResult loc = LocationResult.wrap(l);
+        mDelegateProvider.reportLocation(loc);
+        verify(mListener, times(1)).onReportLocation(loc);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
+    public void testNoThrottle_locationSettingsIgnored() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+                50).setLocationSettingsIgnored(true).build();
+
+        mProvider.getController().setRequest(request);
+        verify(mDelegate).onSetRequest(request);
+
+        LocationResult loc = createLocationResult("test_provider", mRandom);
+        mDelegateProvider.reportLocation(loc);
+        verify(mListener, times(1)).onReportLocation(loc);
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+        verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
new file mode 100644
index 0000000..195cc01
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -0,0 +1,725 @@
+/*
+ * 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.android.server.pm;
+
+import android.apex.ApexSessionInfo;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode;
+import android.os.SystemProperties;
+import android.os.storage.IStorageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class StagingManagerTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Mock private Context mContext;
+    @Mock private IStorageManager mStorageManager;
+    @Mock private ApexManager mApexManager;
+
+    private File mTmpDir;
+    private StagingManager mStagingManager;
+
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
+
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                    .strictness(Strictness.LENIENT)
+                    .mockStatic(SystemProperties.class)
+                    .mockStatic(PackageHelper.class)
+                    .startMocking();
+
+        when(mStorageManager.supportsCheckpoint()).thenReturn(true);
+        when(mStorageManager.needsCheckpoint()).thenReturn(true);
+        when(PackageHelper.getStorageManager()).thenReturn(mStorageManager);
+
+        when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true");
+        when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true");
+
+        mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
+        mStagingManager = new StagingManager(mContext, null, mApexManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
+     * check.
+     */
+    @Test
+    public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
+            throws Exception {
+        // Create 2 sessions with overlapping packages
+        StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
+        StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
+
+        mStagingManager.createSession(session1);
+        mStagingManager.createSession(session2);
+        // Session1 should not fail in spite of the overlapping packages
+        mStagingManager.checkNonOverlappingWithStagedSessions(session1);
+        // Session2 should fail due to overlapping packages
+        assertThrows(PackageManagerException.class,
+                () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
+    }
+
+    @Test
+    public void restoreSessions_nonParentSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+        session.setParentSessionId(1543);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_nonCommittedSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_terminalSession_throwsIAE() throws Exception {
+        FakeStagedSession session = new FakeStagedSession(239);
+        session.setCommitted(true);
+        session.setSessionApplied();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session), false));
+    }
+
+    @Test
+    public void restoreSessions_deviceUpgrading_failsAllSessions() throws Exception {
+        FakeStagedSession session1 = new FakeStagedSession(37);
+        session1.setCommitted(true);
+        FakeStagedSession session2 = new FakeStagedSession(57);
+        session2.setCommitted(true);
+
+        mStagingManager.restoreSessions(Arrays.asList(session1, session2), true);
+
+        assertThat(session1.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session1.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+
+        assertThat(session2.getErrorCode()).isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(session2.getErrorMessage()).isEqualTo("Build fingerprint has changed");
+    }
+
+    @Test
+    public void restoreSessions_multipleSessions_deviceWithoutFsCheckpointSupport_throwISE()
+            throws Exception {
+        FakeStagedSession session1 = new FakeStagedSession(37);
+        session1.setCommitted(true);
+        FakeStagedSession session2 = new FakeStagedSession(57);
+        session2.setCommitted(true);
+
+        when(mStorageManager.supportsCheckpoint()).thenReturn(false);
+
+        assertThrows(IllegalStateException.class,
+                () -> mStagingManager.restoreSessions(Arrays.asList(session1, session2), false));
+    }
+
+    @Test
+    public void restoreSessions_handlesDestroyedAndNotReadySessions() throws Exception {
+        FakeStagedSession destroyedApkSession = new FakeStagedSession(23);
+        destroyedApkSession.setCommitted(true);
+        destroyedApkSession.setDestroyed(true);
+
+        FakeStagedSession destroyedApexSession = new FakeStagedSession(37);
+        destroyedApexSession.setCommitted(true);
+        destroyedApexSession.setDestroyed(true);
+        destroyedApexSession.setIsApex(true);
+
+        FakeStagedSession nonReadyApkSession = new FakeStagedSession(57);
+        nonReadyApkSession.setCommitted(true);
+
+        FakeStagedSession nonReadyApexSession = new FakeStagedSession(73);
+        nonReadyApexSession.setCommitted(true);
+        nonReadyApexSession.setIsApex(true);
+
+        FakeStagedSession destroyedNonReadySession = new FakeStagedSession(101);
+        destroyedNonReadySession.setCommitted(true);
+        destroyedNonReadySession.setDestroyed(true);
+
+        FakeStagedSession regularApkSession = new FakeStagedSession(239);
+        regularApkSession.setCommitted(true);
+        regularApkSession.setSessionReady();
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(destroyedApkSession);
+        sessions.add(destroyedApexSession);
+        sessions.add(nonReadyApkSession);
+        sessions.add(nonReadyApexSession);
+        sessions.add(destroyedNonReadySession);
+        sessions.add(regularApkSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        assertThat(sessions).containsExactly(regularApkSession);
+        assertThat(destroyedApkSession.isDestroyed()).isTrue();
+        assertThat(destroyedApexSession.isDestroyed()).isTrue();
+        assertThat(destroyedNonReadySession.isDestroyed()).isTrue();
+
+        mStagingManager.onBootCompletedBroadcastReceived();
+        assertThat(nonReadyApkSession.hasPreRebootVerificationStarted()).isTrue();
+        assertThat(nonReadyApexSession.hasPreRebootVerificationStarted()).isTrue();
+    }
+
+    @Test
+    public void restoreSessions_unknownApexSession_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        when(mApexManager.getSessions()).thenReturn(new SparseArray<>());
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+                + "staged session supposed to be activated");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_failedApexSessions_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+        apexSession1.setCommitted(true);
+        apexSession1.setIsApex(true);
+        apexSession1.setSessionReady();
+
+        FakeStagedSession apexSession2 = new FakeStagedSession(101);
+        apexSession2.setCommitted(true);
+        apexSession2.setIsApex(true);
+        apexSession2.setSessionReady();
+
+        FakeStagedSession apexSession3 = new FakeStagedSession(57);
+        apexSession3.setCommitted(true);
+        apexSession3.setIsApex(true);
+        apexSession3.setSessionReady();
+
+        ApexSessionInfo activationFailed = new ApexSessionInfo();
+        activationFailed.sessionId = 1543;
+        activationFailed.isActivationFailed = true;
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 101;
+        staged.isStaged = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, activationFailed);
+        apexdSessions.put(101, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession1);
+        sessions.add(apexSession2);
+        sessions.add(apexSession3);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession1.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession1.getErrorMessage()).isEqualTo("APEX activation failed. Check logcat "
+                + "messages from apexd for more information.");
+
+        assertThat(apexSession2.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession2.getErrorMessage()).isEqualTo("Staged session 101 at boot didn't "
+                + "activate nor fail. Marking it as failed anyway.");
+
+        assertThat(apexSession3.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession3.getErrorMessage()).isEqualTo("apexd did not know anything about a "
+                + "staged session supposed to be activated");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_stagedApexSession_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 1543;
+        staged.isStaged = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("Staged session 1543 at boot didn't "
+                + "activate nor fail. Marking it as failed anyway.");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    @Test
+    public void restoreSessions_failedAndActivatedApexSessions_abortsCheckpoint() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession1 = new FakeStagedSession(1543);
+        apexSession1.setCommitted(true);
+        apexSession1.setIsApex(true);
+        apexSession1.setSessionReady();
+
+        FakeStagedSession apexSession2 = new FakeStagedSession(101);
+        apexSession2.setCommitted(true);
+        apexSession2.setIsApex(true);
+        apexSession2.setSessionReady();
+
+        FakeStagedSession apexSession3 = new FakeStagedSession(57);
+        apexSession3.setCommitted(true);
+        apexSession3.setIsApex(true);
+        apexSession3.setSessionReady();
+
+        FakeStagedSession apexSession4 = new FakeStagedSession(37);
+        apexSession4.setCommitted(true);
+        apexSession4.setIsApex(true);
+        apexSession4.setSessionReady();
+
+        ApexSessionInfo activationFailed = new ApexSessionInfo();
+        activationFailed.sessionId = 1543;
+        activationFailed.isActivationFailed = true;
+
+        ApexSessionInfo activated = new ApexSessionInfo();
+        activated.sessionId = 101;
+        activated.isActivated = true;
+
+        ApexSessionInfo staged = new ApexSessionInfo();
+        staged.sessionId = 57;
+        staged.isActivationFailed = true;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, activationFailed);
+        apexdSessions.put(101, activated);
+        apexdSessions.put(57, staged);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession1);
+        sessions.add(apexSession2);
+        sessions.add(apexSession3);
+        sessions.add(apexSession4);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint was aborted.
+        verify(mStorageManager, times(1)).abortChanges(eq("abort-staged-install"), eq(false));
+    }
+
+    @Test
+    public void restoreSessions_apexSessionInImpossibleState_failsAllSessions() throws Exception {
+        FakeStagedSession apkSession = new FakeStagedSession(239);
+        apkSession.setCommitted(true);
+        apkSession.setSessionReady();
+
+        FakeStagedSession apexSession = new FakeStagedSession(1543);
+        apexSession.setCommitted(true);
+        apexSession.setIsApex(true);
+        apexSession.setSessionReady();
+
+        ApexSessionInfo impossible  = new ApexSessionInfo();
+        impossible.sessionId = 1543;
+
+        SparseArray<ApexSessionInfo> apexdSessions = new SparseArray<>();
+        apexdSessions.put(1543, impossible);
+        when(mApexManager.getSessions()).thenReturn(apexdSessions);
+
+        List<StagingManager.StagedSession> sessions = new ArrayList<>();
+        sessions.add(apkSession);
+        sessions.add(apexSession);
+
+        mStagingManager.restoreSessions(sessions, false);
+
+        // Validate checkpoint wasn't aborted.
+        verify(mStorageManager, never()).abortChanges(eq("abort-staged-install"), eq(false));
+
+        assertThat(apexSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apexSession.getErrorMessage()).isEqualTo("Impossible state");
+
+        assertThat(apkSession.getErrorCode())
+                .isEqualTo(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED);
+        assertThat(apkSession.getErrorMessage()).isEqualTo("Another apex session failed");
+    }
+
+    private StagingManager.StagedSession createSession(int sessionId, String packageName,
+            long committedMillis) {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.isStaged = true;
+
+        InstallSource installSource = InstallSource.create("testInstallInitiator",
+                "testInstallOriginator", "testInstaller", "testAttributionTag");
+
+        PackageInstallerSession session = new PackageInstallerSession(
+                /* callback */ null,
+                /* context */ null,
+                /* pm */ null,
+                /* sessionProvider */ null,
+                /* looper */ BackgroundThread.getHandler().getLooper(),
+                /* stagingManager */ null,
+                /* sessionId */ sessionId,
+                /* userId */ 456,
+                /* installerUid */ -1,
+                /* installSource */ installSource,
+                /* sessionParams */ params,
+                /* createdMillis */ 0L,
+                /* committedMillis */ committedMillis,
+                /* stageDir */ mTmpDir,
+                /* stageCid */ null,
+                /* files */ null,
+                /* checksums */ null,
+                /* prepared */ true,
+                /* committed */ true,
+                /* destroyed */ false,
+                /* sealed */ false,  // Setting to true would trigger some PM logic.
+                /* childSessionIds */ null,
+                /* parentSessionId */ -1,
+                /* isReady */ false,
+                /* isFailed */ false,
+                /* isApplied */false,
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
+                /* stagedSessionErrorMessage */ "no error");
+
+        StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
+        doReturn(packageName).when(stagedSession).getPackageName();
+        doAnswer(invocation -> {
+            Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
+            return filter.test(stagedSession);
+        }).when(stagedSession).sessionContains(any());
+        return stagedSession;
+    }
+
+    private static final class FakeStagedSession implements StagingManager.StagedSession {
+        private final int mSessionId;
+        private boolean mIsApex = false;
+        private boolean mIsCommitted = false;
+        private boolean mIsReady = false;
+        private boolean mIsApplied = false;
+        private boolean mIsFailed = false;
+        private @StagedSessionErrorCode int mErrorCode = -1;
+        private String mErrorMessage;
+        private boolean mIsDestroyed = false;
+        private int mParentSessionId = -1;
+        private String mPackageName;
+        private boolean mIsAbandonded = false;
+        private boolean mPreRebootVerificationStarted = false;
+        private final List<StagingManager.StagedSession> mChildSessions = new ArrayList<>();
+
+        private FakeStagedSession(int sessionId) {
+            mSessionId = sessionId;
+        }
+
+        private void setParentSessionId(int parentSessionId) {
+            mParentSessionId = parentSessionId;
+        }
+
+        private void setCommitted(boolean isCommitted) {
+            mIsCommitted = isCommitted;
+        }
+
+        private void setIsApex(boolean isApex) {
+            mIsApex = isApex;
+        }
+
+        private void setDestroyed(boolean isDestroyed) {
+            mIsDestroyed = isDestroyed;
+        }
+
+        private void setPackageName(String packageName) {
+            mPackageName = packageName;
+        }
+
+        private boolean isAbandonded() {
+            return mIsAbandonded;
+        }
+
+        private boolean hasPreRebootVerificationStarted() {
+            return mPreRebootVerificationStarted;
+        }
+
+        private FakeStagedSession addChildSession(FakeStagedSession session) {
+            mChildSessions.add(session);
+            session.setParentSessionId(sessionId());
+            return this;
+        }
+
+        private @StagedSessionErrorCode int getErrorCode() {
+            return mErrorCode;
+        }
+
+        private String getErrorMessage() {
+            return mErrorMessage;
+        }
+
+        @Override
+        public boolean isMultiPackage() {
+            return !mChildSessions.isEmpty();
+        }
+
+        @Override
+        public boolean isApexSession() {
+            return mIsApex;
+        }
+
+        @Override
+        public boolean isCommitted() {
+            return mIsCommitted;
+        }
+
+        @Override
+        public boolean isInTerminalState() {
+            return isSessionApplied() || isSessionFailed();
+        }
+
+        @Override
+        public boolean isDestroyed() {
+            return mIsDestroyed;
+        }
+
+        @Override
+        public boolean isSessionReady() {
+            return mIsReady;
+        }
+
+        @Override
+        public boolean isSessionApplied() {
+            return mIsApplied;
+        }
+
+        @Override
+        public boolean isSessionFailed() {
+            return mIsFailed;
+        }
+
+        @Override
+        public List<StagingManager.StagedSession> getChildSessions() {
+            return mChildSessions;
+        }
+
+        @Override
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        @Override
+        public int getParentSessionId() {
+            return mParentSessionId;
+        }
+
+        @Override
+        public int sessionId() {
+            return mSessionId;
+        }
+
+        @Override
+        public PackageInstaller.SessionParams sessionParams() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean sessionContains(Predicate<StagingManager.StagedSession> filter) {
+            return filter.test(this);
+        }
+
+        @Override
+        public boolean containsApkSession() {
+            Preconditions.checkState(!hasParentSessionId(), "Child session");
+            if (!isMultiPackage()) {
+                return !isApexSession();
+            }
+            for (StagingManager.StagedSession session : mChildSessions) {
+                if (!session.isApexSession()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean containsApexSession() {
+            Preconditions.checkState(!hasParentSessionId(), "Child session");
+            if (!isMultiPackage()) {
+                return isApexSession();
+            }
+            for (StagingManager.StagedSession session : mChildSessions) {
+                if (session.isApexSession()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void setSessionReady() {
+            mIsReady = true;
+        }
+
+        @Override
+        public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) {
+            Preconditions.checkState(!mIsApplied, "Already marked as applied");
+            mIsFailed = true;
+            mErrorCode = errorCode;
+            mErrorMessage = errorMessage;
+        }
+
+        @Override
+        public void setSessionApplied() {
+            Preconditions.checkState(!mIsFailed, "Already marked as failed");
+            mIsApplied = true;
+        }
+
+        @Override
+        public void installSession(IntentSender statusReceiver) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean hasParentSessionId() {
+            return mParentSessionId != -1;
+        }
+
+        @Override
+        public long getCommittedMillis() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void abandon() {
+            mIsAbandonded = true;
+        }
+
+        @Override
+        public boolean notifyStartPreRebootVerification() {
+            mPreRebootVerificationStarted = true;
+            // TODO(ioffe): change to true when tests for pre-reboot verification are added.
+            return false;
+        }
+
+        @Override
+        public void notifyEndPreRebootVerification() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void verifySession() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 6561778..f1402ea 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -3,5 +3,4 @@
 per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
 per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
-per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
deleted file mode 100644
index 633957a..0000000
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ /dev/null
@@ -1,757 +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.
- */
-
-package com.android.server;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManagerInternal;
-import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
-import android.hardware.vibrator.IVibrator;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IVibratorStateListener;
-import android.os.Looper;
-import android.os.PowerManagerInternal;
-import android.os.PowerSaveState;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.os.VibratorInfo;
-import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.view.InputDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Tests for {@link VibratorService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:VibratorServiceTest
- */
-@Presubmit
-public class VibratorServiceTest {
-
-    private static final int TEST_TIMEOUT_MILLIS = 1_000;
-    private static final int UID = Process.ROOT_UID;
-    private static final int VIBRATOR_ID = 1;
-    private static final String PACKAGE_NAME = "package";
-    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
-    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
-            .setBatterySaverEnabled(true).build();
-    private static final VibrationAttributes ALARM_ATTRS =
-            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_TOUCH).build();
-    private static final VibrationAttributes NOTIFICATION_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_NOTIFICATION).build();
-    private static final VibrationAttributes RINGTONE_ATTRS =
-            new VibrationAttributes.Builder().setUsage(
-                    VibrationAttributes.USAGE_RINGTONE).build();
-
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock private AppOpsManager mAppOpsManagerMock;
-    @Mock private IVibratorStateListener mVibratorStateListenerMock;
-    @Mock private IInputManager mIInputManagerMock;
-    @Mock private IBinder mVibratorStateListenerBinderMock;
-
-    private TestLooper mTestLooper;
-    private ContextWrapper mContextSpy;
-    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
-    private FakeVibrator mFakeVibrator;
-    private FakeVibratorControllerProvider mVibratorProvider;
-
-    @Before
-    public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mFakeVibrator = new FakeVibrator();
-        mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper());
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
-
-        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
-        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator);
-        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
-        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
-        when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock);
-        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
-                .thenReturn(new ComponentName("", ""));
-        doAnswer(invocation -> {
-            mRegisteredPowerModeListener = invocation.getArgument(0);
-            return null;
-        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
-        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InputManager.clearInstance();
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-    }
-
-    private VibratorService createService() {
-        VibratorService service = new VibratorService(mContextSpy,
-                new VibratorService.Injector() {
-                    @Override
-                    VibratorController createVibratorController(
-                            VibratorController.OnVibrationCompleteListener listener) {
-                        return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener);
-                    }
-
-                    @Override
-                    Handler createHandler(Looper looper) {
-                        return new Handler(mTestLooper.getLooper());
-                    }
-
-                    @Override
-                    void addService(String name, IBinder service) {
-                        // ignore
-                    }
-                });
-        service.systemReady();
-        return service;
-    }
-
-    @Test
-    public void createService_initializesNativeService() {
-        createService();
-        assertTrue(mVibratorProvider.isInitialized());
-    }
-
-    @Test
-    public void hasVibrator_withVibratorHalPresent_returnsTrue() {
-        assertTrue(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasVibrator_withNoVibratorHalPresent_returnsFalse() {
-        mVibratorProvider.disableVibrators();
-        assertFalse(createService().hasVibrator());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() {
-        assertFalse(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        assertTrue(createService().hasAmplitudeControl());
-    }
-
-    @Test
-    public void getVibratorInfo_returnsSameInfoFromNative() {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
-
-        VibratorInfo info = createService().getVibratorInfo();
-        assertTrue(info.hasAmplitudeControl());
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
-                info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
-        assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
-                info.isEffectSupported(VibrationEffect.EFFECT_TICK));
-        assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
-        assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
-    }
-
-    @Test
-    public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
-        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS);
-
-        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
-        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
-        vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(2, effects.size());
-        assertEquals(10, effects.get(0).getDuration());
-        assertEquals(100, effects.get(1).getDuration());
-    }
-
-    @Test
-    public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception {
-        VibratorService service = createService();
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
-        vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(3, effects.size());
-        assertEquals(2, effects.get(0).getDuration());
-        assertEquals(3, effects.get(1).getDuration());
-        assertEquals(4, effects.get(2).getDuration());
-    }
-
-    @Test
-    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
-        VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
-                audioAttributes, effect).build();
-
-        vibrate(service, effect, vibrationAttributes);
-
-        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                new VibrationAttributes.Builder().setUsage(
-                        VibrationAttributes.USAGE_UNKNOWN).build());
-
-        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
-                anyInt(), anyString());
-        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
-                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createOneShot(100, 128);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes());
-    }
-
-    @Test
-    public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude()
-            throws Exception {
-        VibratorService service = createService();
-        clearInvocations();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS);
-
-        List<VibrationEffect> effects = mVibratorProvider.getEffects();
-        assertEquals(1, effects.size());
-        assertEquals(100, effects.get(0).getDuration());
-        assertTrue(mVibratorProvider.getAmplitudes().isEmpty());
-    }
-
-    @Test
-    public void vibrate_withPrebaked_performsEffect() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked(
-                VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG);
-        assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices()
-            throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), any(), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception {
-        VibratorService service = createService();
-        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
-        service.updateVibrators();
-
-        // Settings callback is async, so wait before checking it never got cancelled.
-        assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withComposed_performsEffect() throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
-                .compose();
-        vibrate(service, effect, ALARM_ATTRS);
-        InOrder inOrderVerifier = inOrder(mIInputManagerMock);
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-        inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime()
-            throws Exception {
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-
-        assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes());
-        assertEquals(
-                Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)),
-                mVibratorProvider.getEffects());
-    }
-
-    @Test
-    public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception {
-        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
-        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1);
-        vibrate(service, effect, ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-
-        // VibrationThread will start this vibration async, so wait before checking it never played.
-        assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service,
-                /* timeout= */ 20));
-    }
-
-    @Test
-    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
-        mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception {
-        VibratorService service = createService();
-
-        vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.cancelVibrate(service);
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-    }
-
-    @Test
-    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
-        VibratorService service = createService();
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
-        VibratorService service = createService();
-
-        service.registerVibratorStateListener(mVibratorStateListenerMock);
-
-        vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
-        assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        service.unregisterVibratorStateListener(mVibratorStateListenerMock);
-        // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(50);
-        mTestLooper.dispatchAll();
-        assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS));
-
-        InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
-        // First notification done when listener is registered.
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false));
-        inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true));
-        inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister
-        inOrderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception {
-        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        mVibratorProvider.setSupportedEffects(
-                VibrationEffect.EFFECT_CLICK,
-                VibrationEffect.EFFECT_TICK,
-                VibrationEffect.EFFECT_DOUBLE_CLICK,
-                VibrationEffect.EFFECT_HEAVY_CLICK);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
-                NOTIFICATION_ATTRS);
-        vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
-
-        List<Integer> playedStrengths = mVibratorProvider.getEffects().stream()
-                .map(VibrationEffect.Prebaked.class::cast)
-                .map(VibrationEffect.Prebaked::getEffectStrength)
-                .collect(Collectors.toList());
-        assertEquals(Arrays.asList(
-                VibrationEffect.EFFECT_STRENGTH_STRONG,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                playedStrengths);
-    }
-
-    @Test
-    public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
-        VibratorService service = createService();
-
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS);
-        vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS);
-        vibrateAndWait(service,
-                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
-                HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS);
-
-        List<Integer> amplitudes = mVibratorProvider.getAmplitudes();
-        assertEquals(3, amplitudes.size());
-        // Alarm vibration is never scaled.
-        assertEquals(100, amplitudes.get(0).intValue());
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertTrue(amplitudes.get(1) > 150);
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50);
-    }
-
-    @Test
-    public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception {
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-
-        mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
-        VibratorService service = createService();
-
-        VibrationEffect effect = VibrationEffect.startComposition()
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
-                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
-                .compose();
-
-        vibrateAndWait(service, effect, ALARM_ATTRS);
-        vibrateAndWait(service, effect, NOTIFICATION_ATTRS);
-        vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS);
-        vibrate(service, effect, RINGTONE_ATTRS);
-
-        List<VibrationEffect.Composition.PrimitiveEffect> primitives =
-                mVibratorProvider.getEffects().stream()
-                        .map(VibrationEffect.Composed.class::cast)
-                        .map(VibrationEffect.Composed::getPrimitiveEffects)
-                        .flatMap(List::stream)
-                        .collect(Collectors.toList());
-
-        // Ringtone vibration is off, so only the other 3 are propagated to native.
-        assertEquals(6, primitives.size());
-
-        // Alarm vibration is never scaled.
-        assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2);
-        assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2);
-
-        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
-        assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2);
-        assertTrue(0.7 < primitives.get(3).scale);
-
-        // Haptic feedback vibrations will be scaled with SCALE_LOW.
-        assertTrue(0.5 < primitives.get(4).scale);
-        assertTrue(0.5 > primitives.get(5).scale);
-    }
-
-    private void vibrate(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) {
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-    }
-
-    private void vibrateAndWait(VibratorService service, VibrationEffect effect,
-            VibrationAttributes attrs) throws Exception {
-        CountDownLatch startedCount = new CountDownLatch(1);
-        CountDownLatch finishedCount = new CountDownLatch(1);
-        service.registerVibratorStateListener(new IVibratorStateListener() {
-            @Override
-            public void onVibrating(boolean vibrating) {
-                if (vibrating) {
-                    startedCount.countDown();
-                } else if (startedCount.getCount() == 0) {
-                    finishedCount.countDown();
-                }
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return mock(IBinder.class);
-            }
-        });
-
-        mTestLooper.startAutoDispatch();
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
-        assertTrue(startedCount.await(1, TimeUnit.SECONDS));
-        assertTrue(finishedCount.await(1, TimeUnit.SECONDS));
-        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
-    }
-
-    private InputDevice createInputDeviceWithVibrator(int id) {
-        return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
-                false /* hasBattery */);
-    }
-
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void setRingerMode(int ringerMode) {
-        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
-        audioManager.setRingerModeInternal(ringerMode);
-        assertEquals(ringerMode, audioManager.getRingerModeInternal());
-    }
-
-    private void setUserSetting(String settingName, int value) {
-        Settings.System.putIntForUser(
-                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-    }
-
-    private void setGlobalSetting(String settingName, int value) {
-        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
-    }
-
-    private boolean waitUntil(Predicate<VibratorService> predicate,
-            VibratorService service, long timeout) throws InterruptedException {
-        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
-        boolean predicateResult = false;
-        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
-            Thread.sleep(10);
-            predicateResult = predicate.test(service);
-        }
-        return predicateResult;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index fdf5095..a946534 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.Context;
@@ -30,15 +28,12 @@
 import android.hardware.power.stats.StateResidencyResult;
 import android.power.PowerStatsInternal;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.power.MeasuredEnergyArray;
 
 import org.junit.Before;
-import org.junit.Test;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -63,44 +58,6 @@
                 mBatteryStatsImpl);
     }
 
-    @Test
-    public void getEnergyConsumptionData() {
-        SparseLongArray expectSubsystems = new SparseLongArray();
-        // Add some energy consumers used by BatteryExternalStatsWorker.
-        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
-                "display");
-        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
-        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
-
-        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
-        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
-        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
-        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
-
-        // Inform BESW that PowerStatsInternal is ready to query
-        mBatteryExternalStatsWorker.systemServicesReady();
-
-        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
-
-        assertEquals(expectSubsystems.size(), energies.size());
-        final int size = expectSubsystems.size();
-
-        for (int i = 0; i < size; i++) {
-            int subsystem = expectSubsystems.keyAt(i);
-            // find the subsystem in the returned MeasuredEnergyArray
-            int subsystemIndex = -1;
-            for (int j = 0; j < size; j++) {
-                if (subsystem == energies.getSubsystem(i)) {
-                    subsystemIndex = i;
-                    break;
-                }
-            }
-            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
-                    subsystemIndex);
-            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
-        }
-    }
-
     public class TestInjector extends BatteryExternalStatsWorker.Injector {
         public TestInjector(Context context) {
             super(context);
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 67d379a..1efce39 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,18 +16,23 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.power.MeasuredEnergyArray;
+import com.android.server.am.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
 
-import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -38,134 +43,198 @@
  */
 @SmallTest
 public final class MeasuredEnergySnapshotTest {
-    private static final int NUMBER_SUBSYSTEMS = 3;
-    private static final int SUBSYSTEM_DISPLAY = 0;
-    private static final int SUBSYSTEM_NEVER_USED = 1;
-    private static final int SUBSYSTEM_CATAPULT = 2;
+    private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
+            0, 0, EnergyConsumerType.DISPLAY, "Display");
+    private static final  EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
+            47, 0, EnergyConsumerType.OTHER, "GPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_1 = createEnergyConsumer(
+            1, 1, EnergyConsumerType.OTHER, "HPU");
+    private static final  EnergyConsumer CONSUMER_OTHER_2 = createEnergyConsumer(
+            436, 2, EnergyConsumerType.OTHER, "IPU");
 
-    private MeasuredEnergySnapshot mSnapshot;
+    private static final SparseArray<EnergyConsumer> ALL_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY, CONSUMER_OTHER_0, CONSUMER_OTHER_1, CONSUMER_OTHER_2);
+    private static final SparseArray<EnergyConsumer> SOME_ID_CONSUMER_MAP = createIdToConsumerMap(
+            CONSUMER_DISPLAY);
 
-    // Basic MeasuredEnergyArray that supports all the subsystems. Out of order on purpose.
-    private final int[] mAllSubsystems =
-            {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT, SUBSYSTEM_NEVER_USED};
-    // E.g. mAllSubsystems[mSubsystemIndices[SUBSYSTEM_CATAPULT]]=SUBSYSTEM_CATAPULT
-    private final int[] mSubsystemIndices = {0, 2, 1};
-    private final long[] mCurrentSubsystemEnergyUJ = {111, 0, 0};
-    private final MeasuredEnergyArray mOmniEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[index];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[index];
-        }
-
-        @Override
-        public int size() {
-            return mAllSubsystems.length;
-        }
+    // Elements in each results are purposefully out of order.
+    private static final  EnergyConsumerResult[] RESULTS_0 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 14, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 0, null, null),
+        // No CONSUMER_OTHER_2
     };
-    private final MeasuredEnergyArray mJustDisplayEnergyArray = new MeasuredEnergyArray() {
-        @Override
-        public int getSubsystem(int index) {
-            return mAllSubsystems[0];
-        }
-
-        @Override
-        public long getEnergy(int index) {
-            return mCurrentSubsystemEnergyUJ[0];
-        }
-
-        @Override
-        public int size() {
-            return 1;
-        }
+    private static final  EnergyConsumerResult[] RESULTS_1 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 24, null, null),
+        createEnergyConsumerResult(CONSUMER_OTHER_0.id, 90, new int[] {47, 3}, new long[] {14, 13}),
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 12, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_2 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 36, null, null),
+        // No CONSUMER_OTHER_0
+        // No CONSUMER_OTHER_1
+        // No CONSUMER_OTHER_2
+    };
+    private static final  EnergyConsumerResult[] RESULTS_3 = new EnergyConsumerResult[] {
+        // No CONSUMER_DISPLAY
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 13, new int[] {6}, new long[] {10}),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 190, new int[] {2, 3, 47, 7}, new long[] {9, 18, 14, 6}),
+        createEnergyConsumerResult(CONSUMER_OTHER_1.id, 12_000, null, null),
+    };
+    private static final  EnergyConsumerResult[] RESULTS_4 = new EnergyConsumerResult[] {
+        createEnergyConsumerResult(CONSUMER_DISPLAY.id, 43, null, null),
+        createEnergyConsumerResult(
+                CONSUMER_OTHER_0.id, 290, new int[] {7, 47, 3, 2}, new long[] {6, 14, 18, 11}),
+        // No CONSUMER_OTHER_1
+        createEnergyConsumerResult(CONSUMER_OTHER_2.id, 165, new int[] {6, 47}, new long[] {10, 8}),
     };
 
-    @Before
-    public void setUp() {
-        mSnapshot = new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, mOmniEnergyArray);
+    @Test
+    public void testUpdateAndGetDelta_empty() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertNull(snapshot.updateAndGetDelta(null));
+        assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0]));
     }
 
     @Test
     public void testUpdateAndGetDelta() {
-        SparseLongArray result;
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
 
-        // Increment DISPLAY by 15
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 15);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(15, result.get(SUBSYSTEM_DISPLAY));
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
 
-        // Increment DISPLAY by 7
-        // Increment CATAPULT by 5. But do NOT include (pull) it in the passed in energy array.
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 7);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 5);
-        result = mSnapshot.updateAndGetDelta(mJustDisplayEnergyArray); // Just pull display.
-        assertEquals(1, result.size());
-        assertEquals(7, result.get(SUBSYSTEM_DISPLAY));
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
 
-        // Increment CATAPULT by 64 (in addition to the previous increase of 5)
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 64);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(5 + 64, result.get(SUBSYSTEM_CATAPULT));
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(90 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 0, delta.otherTotalEnergyUJ[1]);
+        assertEquals(0, delta.otherTotalEnergyUJ[2]); // First good pull. Treat delta as 0.
 
-        // Do nothing
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals("0 results should not appear at all", 0, result.size());
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[0]); // No change in uid energies
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
 
-        // Increment DISPLAY by 42
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 42);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(1, result.size());
-        assertEquals(42, result.get(SUBSYSTEM_DISPLAY));
+        // results2
+        delta = snapshot.updateAndGetDelta(RESULTS_2);
+        assertNotNull(delta);
+        assertEquals(36 - 24, delta.displayEnergyUJ);
+        assertNull(delta.otherUidEnergiesUJ);
+        assertNull(delta.otherTotalEnergyUJ);
 
-        // Increment DISPLAY by 106 and CATAPULT by 13
-        incrementEnergyOfSubsystem(SUBSYSTEM_DISPLAY, 106);
-        incrementEnergyOfSubsystem(SUBSYSTEM_CATAPULT, 13);
-        result = mSnapshot.updateAndGetDelta(mOmniEnergyArray);
-        assertEquals(2, result.size());
-        assertEquals(106, result.get(SUBSYSTEM_DISPLAY));
-        assertEquals(13, result.get(SUBSYSTEM_CATAPULT));
+        // results3
+        delta = snapshot.updateAndGetDelta(RESULTS_3);
+        assertNotNull(delta);
+        assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(190 - 90, delta.otherTotalEnergyUJ[0]);
+        assertEquals(12_000 - 12_000, delta.otherTotalEnergyUJ[1]);
+        assertEquals(13 - 12, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(3, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(9 - 0, delta.otherUidEnergiesUJ[0].get(2));
+        assertEquals(18 - 13, delta.otherUidEnergiesUJ[0].get(3));
+        assertEquals(6 - 0, delta.otherUidEnergiesUJ[0].get(7));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]);
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[2]);
+
+        // results4
+        delta = snapshot.updateAndGetDelta(RESULTS_4);
+        assertNotNull(delta);
+        assertEquals(43 - 36, delta.displayEnergyUJ);
+
+        assertNotNull(delta.otherTotalEnergyUJ);
+        assertEquals(290 - 190, delta.otherTotalEnergyUJ[0]);
+        assertEquals(0, delta.otherTotalEnergyUJ[1]); // Not present (e.g. missing data)
+        assertEquals(165 - 13, delta.otherTotalEnergyUJ[2]);
+
+        assertNotNull(delta.otherUidEnergiesUJ);
+        assertEquals(1, delta.otherUidEnergiesUJ[0].size());
+        assertEquals(11 - 9, delta.otherUidEnergiesUJ[0].get(2));
+        assertNullOrEmpty(delta.otherUidEnergiesUJ[1]); // Not present
+        assertEquals(1, delta.otherUidEnergiesUJ[2].size());
+        assertEquals(8, delta.otherUidEnergiesUJ[2].get(47));
     }
 
-    private void incrementEnergyOfSubsystem(int subsystem, long energy) {
-        mCurrentSubsystemEnergyUJ[mSubsystemIndices[subsystem]] += energy;
+    /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
+    @Test
+    public void testUpdateAndGetDelta_some() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+
+        // results0
+        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0);
+        if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
+            assertEquals(UNAVAILABLE, delta.displayEnergyUJ);
+            assertNull(delta.otherTotalEnergyUJ);
+            assertNull(delta.otherUidEnergiesUJ);
+        }
+
+        // results1
+        delta = snapshot.updateAndGetDelta(RESULTS_1);
+        assertNotNull(delta);
+        assertEquals(24 - 14, delta.displayEnergyUJ);
+        assertNull(delta.otherTotalEnergyUJ); // Although in the results, they're not in the idMap
+        assertNull(delta.otherUidEnergiesUJ);
     }
 
     @Test
-    public void testUpdateAndGetDelta_null() {
-        assertNull(mSnapshot.updateAndGetDelta(null));
+    public void testGetNumOtherOrdinals() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        assertEquals(3, snapshot.getNumOtherOrdinals());
     }
 
     @Test
-    public void testHasSubsystem() {
-        // Setup MeasuredEnergySnapshot which reported some of the subsystems.
-        final int[] subsystems = {SUBSYSTEM_DISPLAY, SUBSYSTEM_CATAPULT};
-        MeasuredEnergyArray measuredEnergyArray = new MeasuredEnergyArray() {
-            @Override
-            public int getSubsystem(int index) {
-                return subsystems[index];
-            }
+    public void testGetNumOtherOrdinals_none() {
+        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        assertEquals(0, snapshot.getNumOtherOrdinals());
+    }
 
-            @Override
-            public long getEnergy(int index) {
-                return 0; // Irrelevant for this test.
-            }
+    private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
+        final EnergyConsumer ec = new EnergyConsumer();
+        ec.id = id;
+        ec.ordinal = ord;
+        ec.type = type;
+        ec.name = name;
+        return ec;
+    }
 
-            @Override
-            public int size() {
-                return subsystems.length;
-            }
-        };
-        final MeasuredEnergySnapshot snapshot =
-                new MeasuredEnergySnapshot(NUMBER_SUBSYSTEMS, measuredEnergyArray);
+    private static SparseArray<EnergyConsumer> createIdToConsumerMap(EnergyConsumer ... ecs) {
+        final SparseArray<EnergyConsumer> map = new SparseArray<>();
+        for (EnergyConsumer ec : ecs) {
+            map.put(ec.id, ec);
+        }
+        return map;
+    }
 
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_DISPLAY));
-        assertTrue(snapshot.hasSubsystem(SUBSYSTEM_CATAPULT));
-        assertFalse(snapshot.hasSubsystem(SUBSYSTEM_NEVER_USED));
+    private static EnergyConsumerResult createEnergyConsumerResult(
+            int id, long energyUWs, int[] uids, long[] uidEnergies) {
+        final EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.id = id;
+        ecr.energyUWs = energyUWs;
+        if (uids != null) {
+            ecr.attribution = new EnergyConsumerAttribution[uids.length];
+            for (int i = 0; i < uids.length; i++) {
+                ecr.attribution[i] = new EnergyConsumerAttribution();
+                ecr.attribution[i].uid = uids[i];
+                ecr.attribution[i].energyUWs = uidEnergies[i];
+            }
+        }
+        return ecr;
+    }
+
+    private void assertNullOrEmpty(SparseLongArray a) {
+        if (a != null) assertEquals("Array should be null or empty", 0, a.size());
     }
 }
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 9b6c723..73b0105 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,8 +16,6 @@
 
 package com.android.server.appsearch.external.localstorage;
 
-import static android.app.appsearch.AppSearchResult.RESULT_OK;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.expectThrows;
@@ -27,7 +25,7 @@
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultPage;
 import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaResult;
+import android.app.appsearch.SetSchemaResponse;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.content.Context;
 import android.util.ArraySet;
@@ -66,14 +64,14 @@
     @Before
     public void setUp() throws Exception {
         Context context = ApplicationProvider.getApplicationContext();
+
         // Give ourselves global query permissions
         mAppSearchImpl =
                 AppSearchImpl.create(
                         mTemporaryFolder.newFolder(),
                         context,
                         VisibilityStore.NO_OP_USER_ID,
-                        /*globalQuerierPackage
-                        =*/ context.getPackageName());
+                        /*globalQuerierPackage=*/ context.getPackageName());
     }
 
     // TODO(b/175430168) add test to verify reset is working properly.
@@ -425,18 +423,24 @@
         GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
 
-        // delete 999 documents , we will reach the threshold to trigger optimize() in next
+        // delete 999 documents, we will reach the threshold to trigger optimize() in next
         // deletion.
         for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
             mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
         }
 
-        // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
+        // Updates the check for optimize counter, checkForOptimize() will be triggered since
+        // CHECK_OPTIMIZE_INTERVAL is reached but optimize() won't since
+        // OPTIMIZE_THRESHOLD_DOC_COUNT is not.
+        mAppSearchImpl.checkForOptimize(
+                /*mutateBatchSize=*/ AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
+
+        // Verify optimize() still not be triggered.
         optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs())
                 .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
 
-        // Keep delete docs, will reach the interval this time and trigger optimize().
+        // Keep delete docs
         for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
                 i
                         < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
@@ -444,6 +448,9 @@
                 i++) {
             mAppSearchImpl.remove("package", "database", "namespace", "uri" + i);
         }
+        // updates the check for optimize counter, will reach both CHECK_OPTIMIZE_INTERVAL and
+        // OPTIMIZE_THRESHOLD_DOC_COUNT this time and trigger a optimize().
+        mAppSearchImpl.checkForOptimize(/*mutateBatchSize*/ AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
 
         // Verify optimize() is triggered
         optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
@@ -783,7 +790,7 @@
                 Collections.singletonList(new AppSearchSchema.Builder("Email").build());
 
         // set email incompatible and delete text
-        SetSchemaResult setSchemaResult =
+        SetSchemaResponse setSchemaResponse =
                 mAppSearchImpl.setSchema(
                         "package",
                         "database1",
@@ -791,9 +798,8 @@
                         /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
                         /*schemasPackageAccessible=*/ Collections.emptyMap(),
                         /*forceOverride=*/ true);
-        assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Text");
-        assertThat(setSchemaResult.getIncompatibleSchemaTypes()).containsExactly("Email");
-        assertThat(setSchemaResult.getResultCode()).isEqualTo(RESULT_OK);
+        assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
+        assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
     }
 
     @Test
@@ -836,7 +842,7 @@
 
         final List<AppSearchSchema> finalSchemas =
                 Collections.singletonList(new AppSearchSchema.Builder("Email").build());
-        SetSchemaResult setSchemaResult =
+        SetSchemaResponse setSchemaResponse =
                 mAppSearchImpl.setSchema(
                         "package",
                         "database1",
@@ -846,7 +852,7 @@
                         /*forceOverride=*/ false);
 
         // Check the Document type has been deleted.
-        assertThat(setSchemaResult.getDeletedSchemaTypes()).containsExactly("Document");
+        assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
 
         // ForceOverride to delete.
         mAppSearchImpl.setSchema(
@@ -1046,4 +1052,136 @@
                                     strippedDocumentProto.build()));
         }
     }
+
+    @Test
+    public void testThrowsExceptionIfClosed() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        AppSearchImpl appSearchImpl =
+                AppSearchImpl.create(
+                        mTemporaryFolder.newFolder(),
+                        context,
+                        VisibilityStore.NO_OP_USER_ID,
+                        /*globalQuerierPackage=*/ "");
+
+        // Initial check that we could do something at first.
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        appSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                /*forceOverride=*/ false);
+
+        appSearchImpl.close();
+
+        // Check all our public APIs
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.setSchema(
+                            "package",
+                            "database",
+                            schemas,
+                            /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+                            /*schemasPackageAccessible=*/ Collections.emptyMap(),
+                            /*forceOverride=*/ false);
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.getSchema("package", "database");
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.putDocument(
+                            "package",
+                            "database",
+                            new GenericDocument.Builder<>("uri", "type")
+                                    .setNamespace("namespace")
+                                    .build());
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.getDocument(
+                            "package", "database", "namespace", "uri", Collections.emptyMap());
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.query(
+                            "package",
+                            "database",
+                            "query",
+                            new SearchSpec.Builder()
+                                    .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                                    .build());
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.globalQuery(
+                            "query",
+                            new SearchSpec.Builder()
+                                    .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                                    .build(),
+                            "package",
+                            /*callerUid=*/ 1);
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.getNextPage(/*nextPageToken=*/ 1L);
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.invalidateNextPageToken(/*nextPageToken=*/ 1L);
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.reportUsage(
+                            "package",
+                            "database",
+                            "namespace",
+                            "uri",
+                            /*usageTimestampMillis=*/ 1000L);
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.remove("package", "database", "namespace", "uri");
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.removeByQuery(
+                            "package",
+                            "database",
+                            "query",
+                            new SearchSpec.Builder()
+                                    .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
+                                    .build());
+                });
+
+        expectThrows(
+                IllegalStateException.class,
+                () -> {
+                    appSearchImpl.persistToDisk();
+                });
+    }
 }
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 8d890b9c..7a4b901 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -103,7 +103,8 @@
     @Test
     public void testNewAuthSession_eligibleSensorsSetToStateUnknown() throws RemoteException {
         setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
-        setupFace(1 /* id */, false /* confirmationAlwaysRequired */);
+        setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
 
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
@@ -118,7 +119,8 @@
 
     @Test
     public void testStartNewAuthSession() throws RemoteException {
-        setupFace(0 /* id */, false /* confirmationAlwaysRequired */);
+        setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+                mock(IBiometricAuthenticator.class));
         setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_REAR);
 
         final boolean requireConfirmation = true;
@@ -181,9 +183,6 @@
 
         final long operationId = 123;
         final int userId = 10;
-        final int callingUid = 100;
-        final int callingPid = 1000;
-        final int callingUserId = 10000;
 
         final AuthSession session = createAuthSession(mSensors,
                 false /* checkDevicePolicyManager */,
@@ -220,7 +219,25 @@
         assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
         assertEquals(BiometricSensor.STATE_AUTHENTICATING,
                 session.mPreAuthInfo.eligibleSensors.get(0).getSensorState());
+    }
 
+    @Test
+    public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
+            throws RemoteException {
+        final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
+
+        setupFace(0 /* id */, false /* confirmationAlwaysRequired */, faceAuthenticator);
+        final AuthSession session = createAuthSession(mSensors,
+                false /* checkDevicePolicyManager */,
+                Authenticators.BIOMETRIC_STRONG,
+                0 /* operationId */,
+                0 /* userId */);
+
+        session.goToInitialState();
+        assertEquals(STATE_AUTH_CALLED, session.getState());
+        session.onCancelAuthSession(false /* force */);
+
+        verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
     }
 
     private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -282,14 +299,14 @@
                 false /* resetLockoutRequiresHardwareAuthToken */));
     }
 
-    private void setupFace(int id, boolean confirmationAlwaysRequired) throws RemoteException {
-        IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
-        when(faceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-        when(faceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+    private void setupFace(int id, boolean confirmationAlwaysRequired,
+            IBiometricAuthenticator authenticator) throws RemoteException {
+        when(authenticator.isHardwareDetected(any())).thenReturn(true);
+        when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
         mSensors.add(new BiometricSensor(id,
                 TYPE_FACE /* modality */,
                 Authenticators.BIOMETRIC_STRONG /* strength */,
-                faceAuthenticator) {
+                authenticator) {
             @Override
             boolean confirmationAlwaysRequired(int userId) {
                 return confirmationAlwaysRequired;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 2d45726..7dd0734 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -27,6 +27,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
 import android.hardware.biometrics.BiometricConstants;
@@ -159,8 +161,8 @@
 
         // Client 1 cleans up properly
         verify(listener1).onError(eq(TEST_SENSOR_ID), anyInt(),
-                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0));
-        verify(callback1).onClientFinished(eq(client1), eq(true) /* success */);
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0));
+        verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
         verify(callback1, never()).onClientStarted(any());
 
         // Client 2 was able to start
@@ -310,6 +312,37 @@
         assertNull(mScheduler.getCurrentClient());
     }
 
+    @Test
+    public void testInterruptPrecedingClients_whenExpected() {
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+                withSettings().extraInterfaces(Interruptable.class));
+
+        final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+        when(interrupter.interruptsPrecedingClients()).thenReturn(true);
+
+        mScheduler.scheduleClientMonitor(interruptableMonitor);
+        mScheduler.scheduleClientMonitor(interrupter);
+        waitForIdle();
+
+        verify((Interruptable) interruptableMonitor).cancel();
+        mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */);
+    }
+
+    @Test
+    public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
+                withSettings().extraInterfaces(Interruptable.class));
+
+        final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
+        when(interrupter.interruptsPrecedingClients()).thenReturn(false);
+
+        mScheduler.scheduleClientMonitor(interruptableMonitor);
+        mScheduler.scheduleClientMonitor(interrupter);
+        waitForIdle();
+
+        verify((Interruptable) interruptableMonitor, never()).cancel();
+    }
+
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
     }
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
index d0767cc..c165c66 100644
--- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -22,6 +22,7 @@
     private boolean mIsDebuggable;
     private int mTargetSdk;
     private String mPackageName;
+    private long mVersionCode;
 
     private ApplicationInfoBuilder() {
         mTargetSdk = -1;
@@ -46,6 +47,11 @@
         return this;
     }
 
+    ApplicationInfoBuilder withVersionCode(Long versionCode) {
+        mVersionCode = versionCode;
+        return this;
+    }
+
     ApplicationInfo build() {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         if (mIsDebuggable) {
@@ -53,6 +59,7 @@
         }
         applicationInfo.packageName = mPackageName;
         applicationInfo.targetSdkVersion = mTargetSdk;
+        applicationInfo.longVersionCode = mVersionCode;
         return applicationInfo;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index a53ff9b..8b0e948 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -25,6 +26,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.compat.ChangeIdStateCache;
+import android.app.compat.PackageOverride;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.CompatibilityOverrideConfig;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +49,7 @@
 import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -83,6 +87,8 @@
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
         ChangeIdStateCache.disable();
+        when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenThrow(new NameNotFoundException());
     }
 
     @Test
@@ -163,6 +169,10 @@
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addDisabledChangeWithId(1234L)
                 .build();
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", true);
 
@@ -177,6 +187,10 @@
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addEnabledChangeWithId(1234L)
                 .build();
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", false);
 
@@ -191,6 +205,10 @@
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
         compatConfig.forceNonDebuggableFinalForTest(false);
 
+        ApplicationInfo info = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(info);
 
         compatConfig.addOverride(1234L, "com.some.package", false);
 
@@ -265,6 +283,71 @@
     }
 
     @Test
+    public void testOverrideWithAppVersion() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.installed.foo")
+                .withVersionCode(100L)
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override that doesn't include the installed app version
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L,
+                        new PackageOverride.Builder()
+                                .setMaxVersionCode(99L)
+                                .setEnabled(true)
+                                .build()));
+        compatConfig.addOverrides(config, "com.installed.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        // Add override that does include the installed app version
+        config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L,
+                        new PackageOverride.Builder()
+                                .setMinVersionCode(100L)
+                                .setMaxVersionCode(100L)
+                                .setEnabled(true)
+                                .build()));
+        compatConfig.addOverrides(config, "com.installed.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.notinstalled.foo")
+                .withVersionCode(100L)
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override before the app is available.
+        CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
+                Collections.singletonMap(1234L, new PackageOverride.Builder()
+                        .setMaxVersionCode(99L)
+                        .setEnabled(true)
+                        .build()));
+        compatConfig.addOverrides(config, "com.notinstalled.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        // Pretend the app is now installed.
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        compatConfig.recheckOverrides("com.notinstalled.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+    }
+
+    @Test
     public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception {
         ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
                 .withPackageName("com.installedapp.foo")
@@ -384,6 +467,8 @@
         ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
                 .withPackageName("com.some.package")
                 .build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+                .thenReturn(applicationInfo);
 
         assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
         assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
@@ -404,6 +489,8 @@
                 .withPackageName("foo.bar")
                 .withTargetSdk(2)
                 .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
 
         assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse();
         assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -425,7 +512,8 @@
                 .withPackageName("foo.bar")
                 .withTargetSdk(2)
                 .build();
-
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
         assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1);
         assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue();
         assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse();
@@ -533,22 +621,114 @@
                 + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
                 + "            </override-value>\n"
                 + "        </validated>\n"
-                + "        <deferred>\n"
-                + "        </deferred>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
                 + "    </change-overrides>\n"
                 + "    <change-overrides changeId=\"2\">\n"
                 + "        <validated>\n"
                 + "        </validated>\n"
-                + "        <deferred>\n"
-                + "            <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
-                + "            </override-value>\n"
-                + "        </deferred>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"bar.baz\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
                 + "    </change-overrides>\n"
                 + "</overrides>\n");
     }
 
     @Test
-    public void testLoadOverrides() throws Exception {
+    public void testSaveOverridesWithRanges() throws Exception {
+        File overridesFile = new File(createTempDir(), "overrides.xml");
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+
+        compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L,
+                new PackageOverride.Builder()
+                        .setMinVersionCode(99L)
+                        .setMaxVersionCode(101L)
+                        .setEnabled(true)
+                        .build())), "foo.bar");
+
+        assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n");
+    }
+
+    @Test
+    public void testLoadOverridesRaw() throws Exception {
+        File tempDir = createTempDir();
+        File overridesFile = new File(tempDir, "overrides.xml");
+        // Change 1 is enabled for foo.bar (validated)
+        // Change 2 is disabled for bar.baz (deferred)
+        String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+                + "            </override-value>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"foo.bar\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "    <change-overrides changeId=\"2\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <raw>\n"
+                + "            <raw-override-value packageName=\"bar.baz\" "
+                + "minVersionCode=\"-9223372036854775808\" "
+                + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n"
+                + "            </raw-override-value>\n"
+                + "        </raw>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n";
+        writeToFile(tempDir, "overrides.xml", xmlData);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("foo.bar")
+                .withVersionCode(100L)
+                .debuggable()
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+        assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
+
+        compatConfig.recheckOverrides("foo.bar");
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testLoadOverridesDeferred() throws Exception {
         File tempDir = createTempDir();
         File overridesFile = new File(tempDir, "overrides.xml");
         // Change 1 is enabled for foo.bar (validated)
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index a1b2dc8..799b067 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -196,6 +196,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -208,6 +211,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -219,6 +225,9 @@
     public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -244,6 +253,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -252,9 +264,12 @@
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+    public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -280,6 +295,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
@@ -299,6 +317,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
@@ -318,6 +339,9 @@
         mPlatformCompat.registerListener(1, mListener1);
         mPlatformCompat.registerListener(2, mListener2);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.setOverrides(
                 CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
@@ -336,6 +360,9 @@
     public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
         mPlatformCompat.registerListener(1, mListener1);
 
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build());
+
         mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         // Listener not called when a non existing override is removed.
         verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 5b0704d..61d7ede 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -28,6 +28,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
+import android.hardware.usb.UsbManager;
 import android.media.IAudioService;
 import android.net.IIpConnectivityMetrics;
 import android.net.Uri;
@@ -244,6 +245,11 @@
         }
 
         @Override
+        UsbManager getUsbManager() {
+            return services.usbManager;
+        }
+
+        @Override
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return services.storageManager.isFileBasedEncryptionEnabled();
         }
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 7597cbf..5bb65ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -89,6 +89,7 @@
 import android.content.pm.StringParceledListSlice;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
+import android.hardware.usb.UsbManager;
 import android.net.Uri;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -4329,6 +4330,68 @@
     }
 
     @Test
+    public void testSetNetworkLoggingEnabled_asPo() throws Exception {
+        final int managedProfileUserId = CALLER_USER_HANDLE;
+        final int managedProfileAdminUid =
+                UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.packageName = admin1.getPackageName();
+        addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
+    public void testSetNetworkLoggingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+        // Setup profile owner on organization-owned device
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mContext.packageName = admin1.getPackageName();
+        mContext.applicationInfo = new ApplicationInfo();
+        when(getServices().iipConnectivityMetrics
+                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+
+        // Check no logs have been retrieved so far.
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Enable network logging
+        dpm.setNetworkLoggingEnabled(admin1, true);
+        assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
+
+        // Retrieve the network logs and verify timestamp has been updated.
+        final long beforeRetrieval = System.currentTimeMillis();
+
+        dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
+
+        final long networkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
+        final long afterRetrieval = System.currentTimeMillis();
+        assertThat(networkLogRetrievalTime >= beforeRetrieval).isTrue();
+        assertThat(networkLogRetrievalTime <= afterRetrieval).isTrue();
+    }
+
+    @Test
     public void testGetBindDeviceAdminTargetUsers() throws Exception {
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
 
@@ -6940,6 +7003,82 @@
                         DevicePolicyManager.PASSWORD_QUALITY_COMPLEX));
     }
 
+    @Test
+    public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+        assertThrows(SecurityException.class,
+                () -> dpm.setUsbDataSignalingEnabled(true));
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+        dpm.setUsbDataSignalingEnabled(false);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+    }
+
+    @Test
+    public void testIsUsbDataSignalingEnabledForUser_systemUser() throws Exception {
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+        setDeviceOwner();
+        dpm.setUsbDataSignalingEnabled(false);
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+
+        assertThat(dpm.isUsbDataSignalingEnabledForUser(UserHandle.myUserId())).isFalse();
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_asPoOfOrgOwnedDevice() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+        mContext.binder.callingUid = managedProfileAdminUid;
+        when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true);
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isTrue();
+
+        dpm.setUsbDataSignalingEnabled(false);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isFalse();
+    }
+
+    @Test
+    public void testCanUsbDataSignalingBeDisabled_canBeDisabled() throws Exception {
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+
+        assertThat(dpm.canUsbDataSignalingBeDisabled()).isTrue();
+    }
+
+    @Test
+    public void testCanUsbDataSignalingBeDisabled_cannotBeDisabled() throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_2);
+
+        assertThat(dpm.canUsbDataSignalingBeDisabled()).isFalse();
+        assertThrows(IllegalStateException.class,
+                () -> dpm.setUsbDataSignalingEnabled(true));
+    }
+
+    @Test
+    public void testSetUsbDataSignalingEnabled_noChangeToActiveAdmin()
+            throws Exception {
+        setDeviceOwner();
+        when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3);
+        boolean enabled = dpm.isUsbDataSignalingEnabled();
+
+        dpm.setUsbDataSignalingEnabled(true);
+
+        assertThat(dpm.isUsbDataSignalingEnabled()).isEqualTo(enabled);
+    }
+
     private void setUserUnlocked(int userHandle, boolean unlocked) {
         when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 944ef8d..f6dee38 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -45,6 +45,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.database.Cursor;
+import android.hardware.usb.UsbManager;
 import android.media.IAudioService;
 import android.net.IIpConnectivityMetrics;
 import android.net.Uri;
@@ -119,6 +120,7 @@
     public final CrossProfileApps crossProfileApps;
     public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
     public final AppOpsManager appOpsManager;
+    public final UsbManager usbManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -163,6 +165,7 @@
         crossProfileApps = mock(CrossProfileApps.class);
         persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
         appOpsManager = mock(AppOpsManager.class);
+        usbManager = mock(UsbManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
index 7506dd4..743b25f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -125,7 +125,7 @@
     private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception {
         // GIVEN a handler with events
         NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(),
-                mDpmTestable, startingId);
+                mDpmTestable, startingId, DpmMockContext.CALLER_USER_HANDLE);
         // GIVEN network events are sent to the handler.
         for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) {
             ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo",
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 1a22661..a078a77 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -52,7 +52,8 @@
 public final class DeviceStateManagerServiceTest {
     private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
     private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
-    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
+    // A device state that is not reported as being supported for the default test provider.
+    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
 
     private TestDeviceStatePolicy mPolicy;
     private TestDeviceStateProvider mProvider;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
new file mode 100644
index 0000000..b5c8053
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceState}.
+ * <p/>
+ * Run with <code>atest DeviceStateTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateTest {
+    @Test
+    public void testConstruct() {
+        final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
+                "CLOSED" /* name */);
+        assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
+        assertEquals(state.getName(), "CLOSED");
+    }
+
+    @Test
+    public void testConstruct_nullName() {
+        final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
+                null /* name */);
+        assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
+        assertNull(state.getName());
+    }
+
+    @Test
+    public void testConstruct_tooLargeIdentifier() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
+                    null /* name */);
+        });
+    }
+
+    @Test
+    public void testConstruct_tooSmallIdentifier() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
+                    null /* name */);
+        });
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 23a4c2f..732c08c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -30,6 +30,7 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -43,6 +44,7 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessControllerTest {
     private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index f0b4f1b..9396ed2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -30,6 +30,7 @@
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
 import android.util.MathUtils;
 import android.util.Spline;
 
@@ -42,6 +43,7 @@
 import java.util.Arrays;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BrightnessMappingStrategyTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 640d6e5..1c55072 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -45,7 +45,9 @@
 import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.MessageQueue;
 import android.os.Process;
+import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
@@ -76,10 +78,15 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.time.Duration;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.LongStream;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
@@ -193,8 +200,8 @@
         verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
         List<DisplayViewport> viewports = viewportCaptor.getValue();
 
-        // Expect to receive 2 viewports: internal, and virtual
-        assertEquals(2, viewports.size());
+        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+        assertTrue(viewports.size() >= 2);
 
         DisplayViewport virtualViewport = null;
         DisplayViewport internalViewport = null;
@@ -202,7 +209,10 @@
             DisplayViewport v = viewports.get(i);
             switch (v.type) {
                 case DisplayViewport.VIEWPORT_INTERNAL: {
+                    // If more than one internal viewport, this will get overwritten several times,
+                    // which for the purposes of this test is fine.
                     internalViewport = v;
+                    assertTrue(internalViewport.valid);
                     break;
                 }
                 case DisplayViewport.VIEWPORT_EXTERNAL: {
@@ -219,9 +229,6 @@
         assertNotNull(internalViewport);
         assertNotNull(virtualViewport);
 
-        // INTERNAL
-        assertTrue(internalViewport.valid);
-
         // VIRTUAL
         assertEquals(height, virtualViewport.deviceHeight);
         assertEquals(width, virtualViewport.deviceWidth);
@@ -243,10 +250,12 @@
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
 
         final int displayIds[] = bs.getDisplayIds();
-        assertEquals(1, displayIds.length);
-        final int displayId = displayIds[0];
-        DisplayInfo info = bs.getDisplayInfo(displayId);
-        assertEquals(info.type, Display.TYPE_INTERNAL);
+        final int size = displayIds.length;
+        assertTrue(size > 0);
+        for (int i = 0; i < size; i++) {
+            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+            assertEquals(info.type, Display.TYPE_INTERNAL);
+        }
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -257,16 +266,22 @@
         verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
         List<DisplayViewport> viewports = viewportCaptor.getValue();
 
-        // Expect to receive actual viewports: 1 internal
-        assertEquals(1, viewports.size());
+        // Due to the nature of foldables, we may have a different number of viewports than
+        // displays, just verify there's at least one.
+        final int viewportSize = viewports.size();
+        assertTrue(viewportSize > 0);
 
-        DisplayViewport internalViewport = viewports.get(0);
+        // Now verify that each viewport's displayId is valid.
+        Arrays.sort(displayIds);
+        for (int i = 0; i < viewportSize; i++) {
+            DisplayViewport internalViewport = viewports.get(i);
 
-        // INTERNAL is the only one actual display.
-        assertNotNull(internalViewport);
-        assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
-        assertTrue(internalViewport.valid);
-        assertEquals(displayId, internalViewport.displayId);
+            // INTERNAL is the only one actual display.
+            assertNotNull(internalViewport);
+            assertEquals(DisplayViewport.VIEWPORT_INTERNAL, internalViewport.type);
+            assertTrue(internalViewport.valid);
+            assertTrue(Arrays.binarySearch(displayIds, internalViewport.displayId) >= 0);
+        }
     }
 
     @Test
@@ -486,7 +501,6 @@
      * Tests that collection of display color sampling results are sensible.
      */
     @Test
-    @FlakyTest(bugId = 172555744)
     public void testDisplayedContentSampling() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
@@ -937,8 +951,22 @@
         // Would prefer to call displayManager.onStart() directly here but it performs binderService
         // registration which triggers security exceptions when running from a test.
         handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
-        // flush the handler
-        handler.runWithScissors(() -> {}, 0 /* now */);
+        waitForIdleHandler(handler, Duration.ofSeconds(1));
+    }
+
+    private void waitForIdleHandler(Handler handler, Duration timeout) {
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.addIdleHandler(() -> {
+            latch.countDown();
+            // Remove idle handler
+            return false;
+        });
+        try {
+            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Interrupted unexpectedly: " + e);
+        }
     }
 
     private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2e3178b..ee0f2e8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -41,12 +41,13 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -82,6 +83,7 @@
 import java.util.stream.Collectors;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayModeDirectorTest {
     // The tolerance within which we consider something approximately equals.
@@ -588,6 +590,12 @@
 
     @Test
     public void testSensorRegistration() {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
         setPeakRefreshRate(90 /*fps*/);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ac45017..ece0a627 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -20,17 +20,23 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import androidx.test.filters.SmallTest;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.InputStream;
 import java.io.OutputStream;
 
+@SmallTest
+@Presubmit
 public class LogicalDisplayTest {
     private static final int DISPLAY_ID = 0;
     private static final int LAYER_STACK = 0;
@@ -52,6 +58,9 @@
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
         when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
 
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
         DisplayDeviceRepository repo = new DisplayDeviceRepository(
                 new DisplayManagerService.SyncRoot(),
                 new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 196454b..d72606a 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.hardware.display.BrightnessConfiguration;
+import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
@@ -40,6 +41,7 @@
 import java.nio.charset.StandardCharsets;
 
 @SmallTest
+@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class PersistentDataStoreTest {
     private PersistentDataStore mDataStore;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
index 86054e4..27fce3c 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -18,13 +18,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.graphics.FontListParser;
 import android.platform.test.annotations.Presubmit;
+import android.text.FontConfig;
+import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -38,13 +42,19 @@
 public final class PersistentSystemFontConfigTest {
 
     @Test
-    public void testWriteRead() throws IOException, XmlPullParserException {
+    public void testWriteRead() throws Exception {
         long expectedModifiedDate = 1234567890;
         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
         config.lastModifiedDate = expectedModifiedDate;
         config.updatedFontDirs.add("~~abc");
         config.updatedFontDirs.add("~~def");
 
+        FontConfig.FontFamily fontFamily = parseFontFamily(
+                "<family name='test'>"
+                + "  <font>test.ttf</font>"
+                + "</family>");
+        config.fontFamilies.add(fontFamily);
+
         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
             PersistentSystemFontConfig.writeToXml(baos, config);
 
@@ -57,6 +67,7 @@
 
                 assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
                 assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
+                assertThat(another.fontFamilies).containsExactly(fontFamily);
             }
         }
     }
@@ -75,4 +86,11 @@
         }
     }
 
+    private static FontConfig.FontFamily parseFontFamily(String xml) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+        parser.setInput(is, "UTF-8");
+        parser.nextTag();
+        return FontListParser.readFamily(parser, "", null);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index cb83b0f..7771afc 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,12 +22,15 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.FontListParser;
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontUpdateRequest;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
 import android.system.Os;
+import android.text.FontConfig;
+import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -37,7 +40,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -49,6 +54,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @Presubmit
 @SmallTest
@@ -157,7 +163,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -173,6 +183,14 @@
         assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(4);
         // Outdated font dir should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
+        assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
+        assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(foobar.getFontList()).hasSize(2);
+        assertThat(foobar.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
+        assertThat(foobar.getFontList().get(1).getFile())
+                .isEqualTo(dir.getFontFileMap().get("bar.ttf"));
     }
 
     @Test
@@ -184,6 +202,7 @@
                 mConfigFile);
         dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -198,7 +217,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -211,6 +234,7 @@
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -225,7 +249,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -239,6 +267,7 @@
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -253,7 +282,11 @@
                 newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
                 newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
                 newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
-                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "  <font>bar.ttf</font>"
+                        + "</family>")));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -274,6 +307,8 @@
         // We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled
         // fonts.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+        // Font family depending on obsoleted font should be removed.
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -285,6 +320,7 @@
                 new File("/dev/null"));
         dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
+        assertThat(dir.getFontFamilyMap()).isEmpty();
     }
 
     @Test
@@ -295,12 +331,19 @@
                 mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
                 mConfigFile);
         dirForPreparation.loadFontFileMap();
-        dirForPreparation.update(
-                Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='foobar'>"
+                        + "  <font>foo.ttf</font>"
+                        + "</family>")));
         try {
             dirForPreparation.update(Arrays.asList(
                     newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
-                    newFontUpdateRequest("bar,2", "Invalid signature")));
+                    newFontUpdateRequest("bar,2", "Invalid signature"),
+                    newAddFontFamilyRequest("<family name='foobar'>"
+                            + "  <font>foo.ttf</font>"
+                            + "  <font>bar.ttf</font>"
+                            + "</family>")));
             fail("Batch update with invalid signature should fail");
         } catch (FontManagerService.SystemFontException e) {
             // Expected
@@ -313,6 +356,11 @@
         // The state should be rolled back as a whole if one of the update requests fail.
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+        assertThat(dir.getFontFamilyMap()).containsKey("foobar");
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(foobar.getFontList()).hasSize(1);
+        assertThat(foobar.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("foo.ttf"));
     }
 
     @Test
@@ -364,7 +412,7 @@
         dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
         try {
             dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
-            fail("Expect IllegalArgumentException");
+            fail("Expect SystemFontException");
         } catch (FontManagerService.SystemFontException e) {
             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
@@ -440,7 +488,7 @@
 
         try {
             dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
-            fail("Expect IllegalArgumentException");
+            fail("Expect SystemFontException");
         } catch (FontManagerService.SystemFontException e) {
             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
@@ -599,6 +647,128 @@
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
     }
 
+    @Test
+    public void addFontFamily() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='test'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        assertThat(dir.getFontFileMap()).containsKey("test.ttf");
+        assertThat(dir.getFontFamilyMap()).containsKey("test");
+        FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+        assertThat(test.getFontList()).hasSize(1);
+        assertThat(test.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+    }
+
+    @Test
+    public void addFontFamily_noName() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Arrays.asList(
+                    newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                    newAddFontFamilyRequest("<family lang='en'>"
+                            + "  <font>test.ttf</font>"
+                            + "</family>")));
+            fail("Expect NullPointerException");
+        } catch (NullPointerException e) {
+            // Expect
+        }
+    }
+
+    @Test
+    public void addFontFamily_fontNotAvailable() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
+                    + "  <font>test.ttf</font>"
+                    + "</family>")));
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
+        }
+    }
+
+    @Test
+    public void getSystemFontConfig() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+        // We assume we have monospace.
+        assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                // Updating an existing font family.
+                newAddFontFamilyRequest("<family name='monospace'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>"),
+                // Adding a new font family.
+                newAddFontFamilyRequest("<family name='test'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        FontConfig fontConfig = dir.getSystemFontConfig();
+        assertNamedFamilyExists(fontConfig, "monospace");
+        FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace");
+        assertThat(monospace.getFontList()).hasSize(1);
+        assertThat(monospace.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+        assertNamedFamilyExists(fontConfig, "test");
+        assertThat(getLastFamily(fontConfig, "test").getFontList())
+                .isEqualTo(monospace.getFontList());
+    }
+
+    @Test
+    public void getSystemFontConfig_preserveFirstFontFamily() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+        assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+        FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
+        assertThat(firstFontFamily.getName()).isNotEmpty();
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("test,1", GOOD_SIGNATURE),
+                newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+                        + "  <font>test.ttf</font>"
+                        + "</family>")));
+        FontConfig fontConfig = dir.getSystemFontConfig();
+        assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
+        assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
+        FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+        assertThat(updated.getFontList()).hasSize(1);
+        assertThat(updated.getFontList().get(0).getFile())
+                .isEqualTo(dir.getFontFileMap().get("test.ttf"));
+        assertThat(updated).isNotEqualTo(firstFontFamily);
+    }
+
     private FontUpdateRequest newFontUpdateRequest(String content, String signature)
             throws Exception {
         File file = File.createTempFile("font", "ttf", mCacheDir);
@@ -608,10 +778,36 @@
                 signature.getBytes());
     }
 
+    private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+        parser.setInput(is, "UTF-8");
+        parser.nextTag();
+        FontConfig.FontFamily fontFamily = FontListParser.readFamily(parser, "", null);
+        return new FontUpdateRequest(fontFamily);
+    }
+
     private void writeConfig(PersistentSystemFontConfig.Config config,
             File file) throws IOException {
         try (FileOutputStream fos = new FileOutputStream(file)) {
             PersistentSystemFontConfig.writeToXml(fos, config);
         }
     }
+
+    // Returns the last family with the given name, which will be used for creating Typeface.
+    private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
+        List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
+        for (int i = fontFamilies.size() - 1; i >= 0; i--) {
+            if (familyName.equals(fontFamilies.get(i).getName())) {
+                return fontFamilies.get(i);
+            }
+        }
+        return null;
+    }
+
+    private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
+        assertThat(fontConfig.getFontFamilies().stream()
+                .map(FontConfig.FontFamily::getName)
+                .collect(Collectors.toSet())).contains(familyName);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index da9dcb7..7cb72c4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -28,6 +29,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
@@ -214,4 +216,75 @@
 
         verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_UNKNOWN);
     }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_1_4() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(response);
+        mTestLooper.dispatchAll();
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+        mTestLooper.dispatchAll();
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    @Test
+    public void queryDisplayStatus_localDevice_2_0_targetDevice_2_0_unknown() throws Exception {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage reportPowerStatusBroadcast = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, ADDR_BROADCAST, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        mNativeWrapper.onCecMessage(reportPowerStatusBroadcast);
+        mTestLooper.dispatchAll();
+        mPlaybackDevice.addAndStartAction(mDevicePowerStatusAction);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                mPlaybackDevice.mAddress, ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage response = HdmiCecMessageBuilder.buildReportPowerStatus(
+                ADDR_TV, mPlaybackDevice.mAddress, HdmiControlManager.POWER_STATUS_STANDBY);
+        mNativeWrapper.onCecMessage(response);
+        mTestLooper.dispatchAll();
+
+        verify(mCallbackMock).onComplete(HdmiControlManager.POWER_STATUS_STANDBY);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index e6b56ca..5342486 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_INVALID;
@@ -49,6 +50,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
@@ -67,6 +69,7 @@
     private int mPlaybackLogicalAddress;
     private boolean mWokenUp;
     private boolean mStandby;
+    private boolean mActiveMediaSessionsPaused;
 
     @Mock
     private IPowerManager mIPowerManagerMock;
@@ -97,6 +100,11 @@
                     }
 
                     @Override
+                    void pauseActiveMediaSessions() {
+                        mActiveMediaSessionsPaused = true;
+                    }
+
+                    @Override
                     boolean isStandbyMessageReceived() {
                         return mStandby;
                     }
@@ -392,6 +400,54 @@
     }
 
     @Test
+    public void handleRoutingChange_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleRoutingChange_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingChange_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void handleRoutingInformation_otherDevice_None() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
@@ -496,6 +552,52 @@
     }
 
     @Test
+    public void handleRoutingInformation_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleRoutingInformation_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleRoutingInformation_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void handleSetStreamPath() {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
@@ -831,6 +933,52 @@
     }
 
     @Test
+    public void handleActiveSource_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleActiveSource_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleActiveSource_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleActiveSource_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void losingActiveSource_standbyNow_verifyStandbyMessageIsSentOnNextStandby() {
         // As described in b/161097846.
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
@@ -1158,6 +1306,54 @@
     }
 
     @Test
+    public void handleSetStreamPath_otherDevice_ActiveSource_mediaSessionsPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isTrue();
+    }
+
+    @Test
+    public void handleSetStreamPath_otherDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleSetStreamPath_sameDevice_ActiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+                mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
+    public void handleSetStreamPath_sameDevice_InactiveSource_mediaSessionsNotPaused() {
+        mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+                "HdmiCecLocalDevicePlaybackTest");
+        mActiveMediaSessionsPaused = false;
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+        mHdmiCecLocalDevicePlayback.dispatchMessage(message);
+        mTestLooper.dispatchAll();
+        assertThat(mActiveMediaSessionsPaused).isFalse();
+    }
+
+    @Test
     public void oneTouchPlay_SendStandbyOnSleepToTv() {
         mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE,
@@ -1392,4 +1588,46 @@
         assertThat(features.contains(
                 Constants.RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU)).isFalse();
     }
+
+    @Test
+    public void doesNotSupportRecordTvScreen() {
+        HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress,
+                Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+        mNativeWrapper.onCecMessage(recordTvScreen);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_RECORD_TV_SCREEN,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void shouldHandleUserControlPressedAndReleased() {
+        HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
+                ADDR_TV, mPlaybackLogicalAddress,
+                HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP);
+        HdmiCecMessage userControlReleased = HdmiCecMessageBuilder.buildUserControlReleased(
+                ADDR_TV, mPlaybackLogicalAddress);
+
+        mNativeWrapper.onCecMessage(userControlPressed);
+        mTestLooper.dispatchAll();
+
+        // Move past the follower safety timeout
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(2));
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(userControlReleased);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbortPressed = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_PRESSED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        HdmiCecMessage featureAbortReleased = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mPlaybackLogicalAddress, ADDR_TV, Constants.MESSAGE_USER_CONTROL_RELEASED,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0f527f3..4623eb5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,8 +15,11 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -27,6 +30,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioManager;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.IThermalService;
@@ -66,6 +70,7 @@
     private IPowerManager mIPowerManagerMock;
     @Mock
     private IThermalService mIThermalServiceMock;
+    @Mock private AudioManager mAudioManager;
 
     @Before
     public void setUp() {
@@ -101,11 +106,21 @@
                     }
 
                     @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
+
+                    @Override
                     protected PowerManager getPowerManager() {
                         return powerManager;
                     }
 
                     @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
                     protected HdmiCecConfig getHdmiCecConfig() {
                         return hdmiCecConfig;
                     }
@@ -121,9 +136,11 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceTv);
-        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
         hdmiPortInfos[0] =
                 new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+        hdmiPortInfos[1] =
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -302,4 +319,185 @@
         assertThat(features.contains(Constants.RC_PROFILE_TV_THREE)).isFalse();
         assertThat(features.contains(Constants.RC_PROFILE_TV_FOUR)).isFalse();
     }
+
+    @Test
+    public void startArcAction_enable_noAudioDevice() {
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+
+    @Test
+    public void startArcAction_disable_noAudioDevice() {
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_enable_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_disable_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_enable_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_disable_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+    }
+
+    @Test
+    public void handleInitiateArc_noAudioDevice() {
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+    }
+
+    @Test
+    public void handleInitiateArc_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+    }
+
+    @Test
+    public void handleInitiateArc_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+    }
+
+    @Test
+    public void supportsRecordTvScreen() {
+        HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress,
+                Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
+
+        mNativeWrapper.onCecMessage(recordTvScreen);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                mTvLogicalAddress, ADDR_RECORDER_1, Constants.MESSAGE_RECORD_TV_SCREEN,
+                ABORT_UNRECOGNIZED_OPCODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
new file mode 100644
index 0000000..b8dfd56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link ActiveSourceAction} */
+@SmallTest
+@RunWith(JUnit4.class)
+public class PowerStatusMonitorActionTest {
+
+    private Context mContextSpy;
+    private HdmiControlService mHdmiControlService;
+    private FakeNativeWrapper mNativeWrapper;
+
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mPhysicalAddress;
+    private HdmiCecLocalDeviceTv mTvDevice;
+
+    @Mock
+    private IPowerManager mIPowerManagerMock;
+    @Mock
+    private IThermalService mIThermalServiceMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
+                mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
+        when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
+        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
+
+        HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+
+        mHdmiControlService = new HdmiControlService(mContextSpy) {
+            @Override
+            AudioManager getAudioManager() {
+                return new AudioManager() {
+                    @Override
+                    public void setWiredDeviceConnectionState(
+                            int type, int state, String address, String name) {
+                        // Do nothing.
+                    }
+                };
+            }
+
+            @Override
+            void wakeUp() {
+            }
+
+            @Override
+            boolean isPowerStandby() {
+                return false;
+            }
+
+            @Override
+            protected PowerManager getPowerManager() {
+                return powerManager;
+            }
+
+            @Override
+            protected void writeStringSystemProperty(String key, String value) {
+                // do nothing
+            }
+
+            @Override
+            protected HdmiCecConfig getHdmiCecConfig() {
+                return hdmiCecConfig;
+            }
+        };
+
+        Looper looper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(looper);
+        mNativeWrapper = new FakeNativeWrapper();
+        HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+                this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(hdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService);
+        mTvDevice.init();
+        mLocalDevices.add(mTvDevice);
+        mTestLooper.dispatchAll();
+        HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[2];
+        hdmiPortInfo[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+        hdmiPortInfo[1] =
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfo);
+        mHdmiControlService.initService();
+        mPhysicalAddress = 0x0000;
+        mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void sourceDevice_1_4_updatesPowerState() {
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_ON);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        reportPowerStatus(ADDR_PLAYBACK_1, false, HdmiControlManager.POWER_STATUS_STANDBY);
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_STANDBY);
+    }
+
+    private void assertPowerStatus(int logicalAddress, int powerStatus) {
+        HdmiDeviceInfo deviceInfo = mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(
+                logicalAddress);
+        assertThat(deviceInfo).isNotNull();
+        assertThat(deviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus);
+    }
+
+    @Test
+    public void sourceDevice_2_0_doesNotUpdatePowerState() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        reportPowerStatus(ADDR_PLAYBACK_1, true, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(60));
+        mTestLooper.dispatchAll();
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_ON);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_1_4_updatesAll() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        assertPowerStatus(ADDR_PLAYBACK_1, HdmiControlManager.POWER_STATUS_UNKNOWN);
+        assertPowerStatus(ADDR_PLAYBACK_2, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus2);
+    }
+
+    @Test
+    public void mixedSourceDevices_localDevice_2_0_onlyUpdates_1_4() {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mTestLooper.dispatchAll();
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_1, 0x1000);
+        sendMessageFromPlaybackDevice(ADDR_PLAYBACK_2, 0x2000);
+        reportPowerStatus(ADDR_PLAYBACK_2, true, HdmiControlManager.POWER_STATUS_ON);
+
+        PowerStatusMonitorAction action = new PowerStatusMonitorAction(mTvDevice);
+        action.start();
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_1);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+
+        HdmiCecMessage giveDevicePowerStatus2 = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                ADDR_TV,
+                ADDR_PLAYBACK_2);
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus2);
+    }
+
+    private void sendMessageFromPlaybackDevice(int logicalAddress, int physicalAddress) {
+        HdmiCecMessage playbackDevice = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                logicalAddress, physicalAddress, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        mNativeWrapper.onCecMessage(playbackDevice);
+        mTestLooper.dispatchAll();
+    }
+
+    private void reportPowerStatus(int logicalAddress, boolean broadcast, int powerStatus) {
+        int destination = broadcast ? ADDR_BROADCAST : ADDR_TV;
+        HdmiCecMessage reportPowerStatus = HdmiCecMessageBuilder.buildReportPowerStatus(
+                logicalAddress, destination,
+                powerStatus);
+        mNativeWrapper.onCecMessage(reportPowerStatus);
+        mTestLooper.dispatchAll();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 353ac4b..f87d599 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -30,6 +30,7 @@
 import android.util.Pair;
 import android.util.SparseIntArray;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -39,6 +40,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
@@ -51,7 +53,7 @@
     private static final String TAG = "WorkerCountTrackerTest";
 
     private static final double[] EQUAL_PROBABILITY_CDF =
-            buildCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+            buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
                     1.0 / NUM_WORK_TYPES);
 
     private Random mRandom;
@@ -64,18 +66,19 @@
     }
 
     @NonNull
-    private static double[] buildCdf(double pTop, double pEj, double pBg, double pBgUser) {
-        double[] cdf = new double[JobConcurrencyManager.NUM_WORK_TYPES];
+    private static double[] buildWorkTypeCdf(double pTop, double pEj, double pBg, double pBgUser) {
+        return buildCdf(pTop, pEj, pBg, pBgUser);
+    }
+
+    @NonNull
+    private static double[] buildCdf(double... probs) {
+        double[] cdf = new double[probs.length];
         double sum = 0;
 
-        sum += pTop;
-        cdf[0] = sum;
-        sum += pEj;
-        cdf[1] = sum;
-        sum += pBg;
-        cdf[2] = sum;
-        sum += pBgUser;
-        cdf[3] = sum;
+        for (int i = 0; i < probs.length; ++i) {
+            sum += probs[i];
+            cdf[i] = sum;
+        }
 
         if (Double.compare(1, sum) != 0) {
             throw new IllegalArgumentException("probabilities don't sum to one: " + sum);
@@ -83,25 +86,30 @@
         return cdf;
     }
 
-    @JobConcurrencyManager.WorkType
-    static int getRandomWorkType(double[] cdf, double rand) {
+    static int getRandomIndex(double[] cdf, double rand) {
         for (int i = cdf.length - 1; i >= 0; --i) {
             if (rand < cdf[i] && (i == 0 || rand > cdf[i - 1])) {
-                switch (i) {
-                    case 0:
-                        return WORK_TYPE_TOP;
-                    case 1:
-                        return WORK_TYPE_EJ;
-                    case 2:
-                        return WORK_TYPE_BG;
-                    case 3:
-                        return WORK_TYPE_BGUSER;
-                    default:
-                        throw new IllegalStateException("Unknown work type");
-                }
+                return i;
             }
         }
-        throw new IllegalStateException("Couldn't pick random work type");
+        throw new IllegalStateException("Couldn't pick random index");
+    }
+
+    @JobConcurrencyManager.WorkType
+    static int getRandomWorkType(double[] cdf, double rand) {
+        final int index = getRandomIndex(cdf, rand);
+        switch (index) {
+            case 0:
+                return WORK_TYPE_TOP;
+            case 1:
+                return WORK_TYPE_EJ;
+            case 2:
+                return WORK_TYPE_BG;
+            case 3:
+                return WORK_TYPE_BGUSER;
+            default:
+                throw new IllegalStateException("Unknown work type");
+        }
     }
 
     /**
@@ -110,25 +118,59 @@
     class Jobs {
         public final SparseIntArray running = new SparseIntArray();
         public final SparseIntArray pending = new SparseIntArray();
+        public final List<Integer> pendingMultiTypes = new ArrayList<>();
 
-        public void maybeEnqueueJobs(double probStart, double[] typeCdf) {
+        /**
+         * @param probStart   Probability of starting a job
+         * @param typeCdf     The CDF representing the probability of each work type
+         * @param numTypesCdf The CDF representing the probability of a job having X different
+         *                    work types. Each index i represents i+1 work types (ie. index 0 = 1
+         *                    work type, index 3 = 4 work types).
+         */
+        public void maybeEnqueueJobs(double probStart, double[] typeCdf, double[] numTypesCdf) {
+            assertThat(numTypesCdf.length).isAtMost(NUM_WORK_TYPES);
+            assertThat(numTypesCdf.length).isAtLeast(1);
+
             while (mRandom.nextDouble() < probStart) {
-                final int workType = getRandomWorkType(typeCdf, mRandom.nextDouble());
-                pending.put(workType, pending.get(workType) + 1);
+                final int numTypes = getRandomIndex(numTypesCdf, mRandom.nextDouble()) + 1;
+                int types = WORK_TYPE_NONE;
+                for (int i = 0; i < numTypes; ++i) {
+                    types |= getRandomWorkType(typeCdf, mRandom.nextDouble());
+                }
+                addPending(types, 1);
             }
         }
 
-        public void maybeFinishJobs(double probStop) {
-            for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
-                if (mRandom.nextDouble() < probStop) {
-                    running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
-                    mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+        void addPending(int allWorkTypes, int num) {
+            for (int n = 0; n < num; ++n) {
+                for (int i = 0; i < 32; ++i) {
+                    final int type = 1 << i;
+                    if ((allWorkTypes & type) != 0) {
+                        pending.put(type, pending.get(type) + 1);
+                    }
+                }
+                pendingMultiTypes.add(allWorkTypes);
+            }
+        }
+
+        void removePending(int allWorkTypes) {
+            for (int i = 0; i < 32; ++i) {
+                final int type = 1 << i;
+                if ((allWorkTypes & type) != 0) {
+                    pending.put(type, pending.get(type) - 1);
                 }
             }
-            for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
-                if (mRandom.nextDouble() < probStop) {
-                    running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
-                    mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+            pendingMultiTypes.remove(Integer.valueOf(allWorkTypes));
+        }
+
+        public void maybeFinishJobs(double probStop) {
+            for (int i = running.size() - 1; i >= 0; --i) {
+                final int workType = running.keyAt(i);
+                for (int c = running.valueAt(i); c > 0; --c) {
+                    if (mRandom.nextDouble() < probStop) {
+                        running.put(workType, running.get(workType) - 1);
+                        mWorkCountTracker.onJobFinished(workType);
+                    }
                 }
             }
         }
@@ -171,6 +213,15 @@
         return false;
     }
 
+    private int getPendingMultiType(Jobs jobs, @JobConcurrencyManager.WorkType int workType) {
+        for (int multiType : jobs.pendingMultiTypes) {
+            if ((multiType & workType) != 0) {
+                return multiType;
+            }
+        }
+        throw new IllegalStateException("No pending multi type with work type: " + workType);
+    }
+
     private void startPendingJobs(Jobs jobs) {
         while (hasStartablePendingJob(jobs)) {
             final int startingWorkType =
@@ -178,9 +229,10 @@
 
             if (jobs.pending.get(startingWorkType) > 0
                     && mWorkCountTracker.canJobStart(startingWorkType) != WORK_TYPE_NONE) {
-                jobs.pending.put(startingWorkType, jobs.pending.get(startingWorkType) - 1);
+                final int pendingMultiType = getPendingMultiType(jobs, startingWorkType);
+                jobs.removePending(pendingMultiType);
                 jobs.running.put(startingWorkType, jobs.running.get(startingWorkType) + 1);
-                mWorkCountTracker.stageJob(startingWorkType);
+                mWorkCountTracker.stageJob(startingWorkType, pendingMultiType);
                 mWorkCountTracker.onJobStarted(startingWorkType);
             }
         }
@@ -192,12 +244,17 @@
     private void checkRandom(Jobs jobs, int numTests, int totalMax,
             @NonNull List<Pair<Integer, Integer>> minLimits,
             @NonNull List<Pair<Integer, Integer>> maxLimits,
-            double probStart, double[] typeCdf, double probStop) {
+            double probStart, double[] typeCdf, double[] numTypesCdf, double probStop) {
+        int minExpected = 0;
+        for (Pair<Integer, Integer> minLimit : minLimits) {
+            minExpected = Math.min(minLimit.second, minExpected);
+        }
         for (int i = 0; i < numTests; i++) {
             jobs.maybeFinishJobs(probStop);
-            jobs.maybeEnqueueJobs(probStart, typeCdf);
+            jobs.maybeEnqueueJobs(probStart, typeCdf, numTypesCdf);
 
             recount(jobs, totalMax, minLimits, maxLimits);
+            final int numPending = jobs.pendingMultiTypes.size();
             startPendingJobs(jobs);
 
             int totalRunning = 0;
@@ -209,6 +266,7 @@
                 totalRunning += numRunning;
             }
             assertThat(totalRunning).isAtMost(totalMax);
+            assertThat(totalRunning).isAtLeast(Math.min(minExpected, numPending));
             for (Pair<Integer, Integer> maxLimit : maxLimits) {
                 assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
                         .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
@@ -233,7 +291,7 @@
         final double probStart = 0.1;
 
         checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
-                EQUAL_PROBABILITY_CDF, probStop);
+                EQUAL_PROBABILITY_CDF, EQUAL_PROBABILITY_CDF, probStop);
     }
 
     @Test
@@ -246,10 +304,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.5, 0, 0.5, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0);
+        final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -262,10 +322,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] numTypesCdf = buildCdf(.75, .2, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -278,10 +340,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final List<Pair<Integer, Integer>> minLimits = List.of();
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 1.0 / 3, 1.0 / 3);
+        final double[] numTypesCdf = buildCdf(.05, .95);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -294,10 +358,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.1, 0, 0.8, .1);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.8, .1);
+        final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -310,10 +376,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0.9, 0, 0.1, 0);
+        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.1, 0);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -326,10 +394,12 @@
                 List.of(Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 2));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.1, 0, 0.1, .8);
+        final double[] cdf = buildWorkTypeCdf(0.1, 0, 0.1, .8);
+        final double[] numTypesCdf = buildCdf(0.5, 0.5);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -343,10 +413,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.9, 0, 0.05, 0.05);
+        final double[] cdf = buildWorkTypeCdf(0.9, 0, 0.05, 0.05);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -360,10 +432,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.5, 0.5);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.5, 0.5);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -377,10 +451,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.1, 0.9);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.1, 0.9);
+        final double[] numTypesCdf = buildCdf(0.9, 0.1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -394,10 +470,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_BG, 2), Pair.create(WORK_TYPE_BGUSER, 1));
         final double probStop = 0.5;
-        final double[] cdf = buildCdf(0, 0, 0.9, 0.1);
+        final double[] cdf = buildWorkTypeCdf(0, 0, 0.9, 0.1);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -410,13 +488,16 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.5, 0.5, 0, 0);
+        final double[] cdf = buildWorkTypeCdf(0.5, 0.5, 0, 0);
+        final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
+    @LargeTest
     public void testRandom13() {
         assertThat(EQUAL_PROBABILITY_CDF.length).isEqualTo(NUM_WORK_TYPES);
 
@@ -429,11 +510,12 @@
                 Pair.create(WORK_TYPE_BGUSER, 3));
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1));
-        final double probStop = 0.01;
-        final double probStart = 0.99;
+        final double probStop = 0.13;
+        final double[] numTypesCdf = buildCdf(0, 0.05, 0.1, 0.85);
+        final double probStart = 0.87;
 
         checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
-                EQUAL_PROBABILITY_CDF, probStop);
+                EQUAL_PROBABILITY_CDF, numTypesCdf, probStop);
     }
 
     @Test
@@ -446,10 +528,12 @@
                 List.of(Pair.create(WORK_TYPE_EJ, 5), Pair.create(WORK_TYPE_BG, 4));
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(.1, 0.5, 0.35, 0.05);
+        final double[] cdf = buildWorkTypeCdf(.1, 0.5, 0.35, 0.05);
+        final double[] numTypesCdf = buildCdf(1);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     @Test
@@ -464,10 +548,12 @@
         final List<Pair<Integer, Integer>> minLimits =
                 List.of(Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2));
         final double probStop = 0.4;
-        final double[] cdf = buildCdf(0.01, 0.49, 0.1, 0.4);
+        final double[] cdf = buildWorkTypeCdf(0.01, 0.49, 0.1, 0.4);
+        final double[] numTypesCdf = buildCdf(0.7, 0.3);
         final double probStart = 0.5;
 
-        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart, cdf, probStop);
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits, probStart,
+                cdf, numTypesCdf, probStop);
     }
 
     /** Used by the following tests */
@@ -483,7 +569,7 @@
             jobs.running.put(run.first, run.second);
         }
         for (Pair<Integer, Integer> pend : pending) {
-            jobs.pending.put(pend.first, pend.second);
+            jobs.addPending(pend.first, pend.second);
         }
 
         recount(jobs, totalMax, minLimits, maxLimits);
@@ -651,14 +737,32 @@
                         Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
                 /* resPen */ List.of(
                         Pair.create(WORK_TYPE_BG, 1), Pair.create(WORK_TYPE_BGUSER, 2)));
+
+        // Test multi-types
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(
+                        Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)),
+                /* run */ List.of(),
+                /* pen */ List.of(
+                        // 2 of these as TOP, 1 as EJ
+                        Pair.create(WORK_TYPE_TOP | WORK_TYPE_EJ, 3),
+                        // 1 as EJ, 2 as BG
+                        Pair.create(WORK_TYPE_EJ | WORK_TYPE_BG, 3),
+                        Pair.create(WORK_TYPE_BG, 4),
+                        Pair.create(WORK_TYPE_BGUSER, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2),
+                        Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(
+                        Pair.create(WORK_TYPE_BG, 4), Pair.create(WORK_TYPE_BGUSER, 1)));
     }
 
     /** Tests that the counter updates properly when jobs are stopped. */
     @Test
     public void testJobLifecycleLoop() {
         final Jobs jobs = new Jobs();
-        jobs.pending.put(WORK_TYPE_TOP, 11);
-        jobs.pending.put(WORK_TYPE_BG, 10);
+        jobs.addPending(WORK_TYPE_TOP, 11);
+        jobs.addPending(WORK_TYPE_BG, 10);
 
         final int totalMax = 6;
         final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
@@ -729,4 +833,149 @@
         assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
         assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
     }
+
+    /** Tests that the counter updates properly when jobs are stopped. */
+    @Test
+    public void testJobLifecycleLoop_Multitype() {
+        final Jobs jobs = new Jobs();
+        jobs.addPending(WORK_TYPE_TOP, 6); // a
+        jobs.addPending(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+        jobs.addPending(WORK_TYPE_BG, 10); // c
+
+        final int totalMax = 8;
+        final List<Pair<Integer, Integer>> minLimits =
+                List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(5);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+        // Since starting happens in random order, all EJs could have run first.
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+        // Stop all jobs
+        jobs.maybeFinishJobs(1);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+        // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+        // for EJ, which would reduce BG count to 3 instead of 4.
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(5);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(6);
+
+        // Stop only a bg job and make sure the counter only allows another bg job to start.
+        jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+        // Depending on the order jobs start, we may run all TOP/EJ combos as TOP and reserve a slot
+        // for EJ, which would reduce BG count to 3 instead of 4.
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP) + jobs.running.get(WORK_TYPE_EJ)).isEqualTo(4);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtLeast(3);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isAtMost(4);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtLeast(4);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isAtMost(5);
+    }
+
+    /** Tests that the counter updates properly when jobs are stopped. */
+    @Test
+    public void testJobLifecycleLoop_Multitype_RandomOrder() {
+        final Jobs jobs = new Jobs();
+        SparseIntArray multiToCount = new SparseIntArray();
+        multiToCount.put(WORK_TYPE_TOP, 6); // a
+        multiToCount.put(WORK_TYPE_TOP | WORK_TYPE_EJ, 5); // b
+        multiToCount.put(WORK_TYPE_EJ | WORK_TYPE_BG, 5); // c
+        multiToCount.put(WORK_TYPE_BG, 5); // d
+        while (multiToCount.size() > 0) {
+            final int index = mRandom.nextInt(multiToCount.size());
+            final int count = multiToCount.valueAt(index);
+            jobs.addPending(multiToCount.keyAt(index), 1);
+            if (count <= 1) {
+                multiToCount.removeAt(index);
+            } else {
+                multiToCount.put(multiToCount.keyAt(index), count - 1);
+            }
+        }
+
+        final int totalMax = 8;
+        final List<Pair<Integer, Integer>> minLimits =
+                List.of(Pair.create(WORK_TYPE_EJ, 1), Pair.create(WORK_TYPE_BG, 1));
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(11);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isEqualTo(10);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(10);
+
+        startPendingJobs(jobs);
+
+        // Random order, but we should have 6 TOP, 1 EJ, and 1 BG running.
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(6);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isEqualTo(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(4);
+        // Can't equate pending EJ since some could be running as TOP and BG
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+        // Stop all jobs
+        jobs.maybeFinishJobs(1);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_EJ);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        // Random order, but we should have 4 TOP, 1 EJ, and 1 BG running.
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        // At this point, all TOP should be running (or have already run).
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(5);
+
+        // Stop only a bg job and make sure the counter only allows another bg job to start.
+        jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_EJ)).isEqualTo(WORK_TYPE_NONE);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_EJ)).isAtLeast(1);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_EJ)).isAtLeast(2);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(4);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index 25dbc6b..33ea710 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -45,6 +45,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,8 @@
         MockitoAnnotations.initMocks(this);
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
-        mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0,
+                Process.SHELL_UID);
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
index 32445fd..2eedc32 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -19,19 +19,17 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.security.GeneralSecurityException;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
 
-import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
  * atest FrameworksServicesTests:RebootEscrowDataTest
@@ -41,22 +39,18 @@
     private RebootEscrowKey mKey;
     private SecretKey mKeyStoreEncryptionKey;
 
-    private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
-        KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
-        generator.init(new KeyGenParameterSpec.Builder(
-                "reboot_escrow_data_test_key",
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                .setKeySize(256)
-                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                .build());
-        return generator.generateKey();
-    }
+    // Hex encoding of a randomly generated AES key for test.
+    private static final byte[] TEST_AES_KEY = new byte[] {
+            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
+            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
+            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
+            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
+    };
 
     @Before
     public void generateKey() throws Exception {
         mKey = RebootEscrowKey.generate();
-        mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
+        mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
     }
 
     private static byte[] getTestSp() {
@@ -114,4 +108,23 @@
         assertThat(decrypted, is(testSp));
     }
 
+    @Test
+    public void fromEncryptedData_legacyVersion_success() throws Exception {
+        byte[] testSp = getTestSp();
+        byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp);
+
+        // Write a legacy blob encrypted only by k_s.
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+        dos.writeInt(1);
+        dos.writeByte(3);
+        dos.write(ksEncryptedBlob);
+        byte[] legacyBlob = bos.toByteArray();
+
+        RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null);
+
+        assertThat(actual.getSpVersion(), is((byte) 3));
+        assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes()));
+        assertThat(actual.getSyntheticPassword(), is(testSp));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index a4ba4c8..a896f1b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -43,6 +43,7 @@
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
 import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.os.UserManager;
@@ -155,6 +156,11 @@
         }
 
         @Override
+        void post(Handler handler, Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
         public UserManager getUserManager() {
             return mUserManager;
         }
@@ -369,7 +375,7 @@
 
     @Test
     public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
     }
 
     @Test
@@ -401,7 +407,7 @@
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -435,7 +441,7 @@
 
         when(mServiceConnection.unwrap(any(), anyLong()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mServiceConnection).unwrap(any(), anyLong());
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -466,7 +472,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         verify(mInjected, never()).reportMetric(anyBoolean());
     }
@@ -493,7 +499,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mInjected, never()).reportMetric(anyBoolean());
     }
 
@@ -527,7 +533,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mInjected).reportMetric(eq(true));
     }
 
@@ -557,7 +563,7 @@
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]);
-        mService.loadRebootEscrowDataIfAvailable();
+        mService.loadRebootEscrowDataIfAvailable(null);
         verify(mRebootEscrow).retrieveKey();
         assertFalse(metricsSuccessCaptor.getValue());
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
index bc1e025..28b737b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -30,6 +30,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -42,7 +43,6 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.File;
-import java.io.IOException;
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
@@ -130,7 +130,7 @@
     @Test
     public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
         when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
-        doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+        doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong());
 
         assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
         mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 3a292de..38125c7 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -21,7 +21,9 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageInfo
 import android.os.Process
+import android.util.ArrayMap
 import com.android.server.om.OverlayActorEnforcer.ActorState
+import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
@@ -29,6 +31,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
+import org.mockito.Mockito
 import org.mockito.Mockito.spy
 import java.io.IOException
 
@@ -125,11 +128,11 @@
                 ActorState.TARGET_NOT_FOUND withCases {
                     failure("nullPkgInfo") { targetPkgInfo = null }
                     allowed("debuggable") {
-                        targetPkgInfo = pkgInfo(TARGET_PKG).apply {
-                            applicationInfo.flags = ApplicationInfo.FLAG_DEBUGGABLE
+                        targetPkgInfo = androidPackage(TARGET_PKG).apply {
+                            whenever(this.isDebuggable).thenReturn(true)
                         }
                     }
-                    skip { targetPkgInfo = pkgInfo(TARGET_PKG) }
+                    skip { targetPkgInfo = androidPackage(TARGET_PKG) }
                 },
                 ActorState.NO_PACKAGES_FOR_UID withCases {
                     failure("empty") { callingUid = EMPTY_UID }
@@ -236,22 +239,20 @@
                                 mapOf(VALID_ACTOR_NAME to VALID_ACTOR_PKG))
                     }
                 },
-                ActorState.MISSING_APP_INFO withCases {
+                ActorState.ACTOR_NOT_FOUND withCases {
                     failure("nullActorPkgInfo") { actorPkgInfo = null }
                     failure("nullActorAppInfo") {
-                        actorPkgInfo = PackageInfo().apply { applicationInfo = null }
+                        actorPkgInfo = null
                     }
-                    skip { actorPkgInfo = pkgInfo(VALID_ACTOR_PKG) }
+                    skip { actorPkgInfo = androidPackage(VALID_ACTOR_PKG) }
                 },
                 ActorState.ACTOR_NOT_PREINSTALLED withCases {
                     failure("notSystem") {
-                        actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
-                            applicationInfo.flags = 0
-                        }
+                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG)
                     }
                     skip {
-                        actorPkgInfo = pkgInfo(VALID_ACTOR_PKG).apply {
-                            applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM
+                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG).apply {
+                            whenever(this.isSystem).thenReturn(true)
                         }
                     }
                 },
@@ -272,22 +273,22 @@
         ) {
             fun toOverlayInfo() = OverlayInfo(
                     OVERLAY_PKG,
+                    "",
                     targetPackageName,
                     targetOverlayableName,
                     null,
                     "/path",
                     OverlayInfo.STATE_UNKNOWN, 0,
-                    0, false)
+                    0, false, false)
         }
 
         private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
                 TestCase(this).apply(block)
 
-        private fun pkgInfo(pkgName: String): PackageInfo = mockThrowOnUnmocked {
-            this.packageName = pkgName
-            this.applicationInfo = ApplicationInfo().apply {
-                this.packageName = pkgName
-            }
+        private fun androidPackage(pkgName: String): AndroidPackage = mockThrowOnUnmocked {
+            whenever(this.packageName).thenReturn(pkgName)
+            whenever(this.isDebuggable).thenReturn(false)
+            whenever(this.isSystem).thenReturn(false)
         }
 
         private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
@@ -363,8 +364,8 @@
         var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
         var hasPermission: Boolean = false,
         var targetOverlayableInfo: OverlayableInfo? = null,
-        var targetPkgInfo: PackageInfo? = null,
-        var actorPkgInfo: PackageInfo? = null,
+        var targetPkgInfo: AndroidPackage? = null,
+        var actorPkgInfo: AndroidPackage? = null,
         vararg val packageNames: String = arrayOf("com.test.actor.one")
     ) : PackageManagerHelper {
 
@@ -375,6 +376,14 @@
 
         override fun getNamedActors() = namedActorsMap
 
+        override fun isInstantApp(packageName: String, userId: Int): Boolean {
+            throw UnsupportedOperationException()
+        }
+
+        override fun initializeForUser(userId: Int): ArrayMap<String, AndroidPackage> {
+            throw UnsupportedOperationException()
+        }
+
         @Throws(IOException::class)
         override fun getOverlayableForTarget(
             packageName: String,
@@ -394,9 +403,6 @@
             else -> null
         }
 
-        override fun getPackageInfo(packageName: String, userId: Int) =
-                listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
-
         @Throws(IOException::class) // Mockito requires this checked exception to be declared
         override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
             return targetOverlayableInfo?.takeIf {
@@ -411,11 +417,10 @@
             }
         }
 
-        override fun getConfigSignaturePackage(): String {
-            throw UnsupportedOperationException()
-        }
+        override fun getPackageForUser(packageName: String, userId: Int) =
+            listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
 
-        override fun getOverlayPackages(userId: Int): MutableList<PackageInfo> {
+        override fun getConfigSignaturePackage(): String {
             throw UnsupportedOperationException()
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 5468fba..55cd772 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -21,7 +21,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.util.ArraySet;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -29,77 +31,66 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
-import java.util.List;
 import java.util.function.BiConsumer;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceImplTestsBase {
 
     private static final String OVERLAY = "com.test.overlay";
+    private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
     private static final String TARGET = "com.test.target";
     private static final int USER = 0;
 
     private static final String OVERLAY2 = OVERLAY + "2";
+    private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
 
     @Test
     public void testUpdateOverlaysForUser() {
         final OverlayManagerServiceImpl impl = getImpl();
+        final String otherTarget = "some.other.target";
         addPackage(target(TARGET), USER);
-        addPackage(target("some.other.target"), USER);
+        addPackage(target(otherTarget), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
         // do nothing, expect no change
-        final List<String> a = impl.updateOverlaysForUser(USER);
-        assertEquals(1, a.size());
-        assertTrue(a.contains(TARGET));
+        final ArraySet<PackageAndUser> a = impl.updateOverlaysForUser(USER);
+        assertEquals(3, a.size());
+        assertTrue(a.containsAll(Arrays.asList(
+                new PackageAndUser(TARGET, USER),
+                new PackageAndUser(otherTarget, USER),
+                new PackageAndUser(OVERLAY, USER))));
 
-        // upgrade overlay, keep target
-        addPackage(overlay(OVERLAY, TARGET), USER);
-
-        final List<String> b = impl.updateOverlaysForUser(USER);
-        assertEquals(1, b.size());
-        assertTrue(b.contains(TARGET));
-
-        // do nothing, expect no change
-        final List<String> c = impl.updateOverlaysForUser(USER);
-        assertEquals(1, c.size());
-        assertTrue(c.contains(TARGET));
-
-        // upgrade overlay, switch to new target
-        addPackage(overlay(OVERLAY, "some.other.target"), USER);
-        final List<String> d = impl.updateOverlaysForUser(USER);
-        assertEquals(2, d.size());
-        assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
-
-        // do nothing, expect no change
-        final List<String> f = impl.updateOverlaysForUser(USER);
-        assertEquals(1, f.size());
-        assertTrue(f.contains("some.other.target"));
+        final ArraySet<PackageAndUser> b = impl.updateOverlaysForUser(USER);
+        assertEquals(3, b.size());
+        assertTrue(b.containsAll(Arrays.asList(
+                new PackageAndUser(TARGET, USER),
+                new PackageAndUser(otherTarget, USER),
+                new PackageAndUser(OVERLAY, USER))));
     }
 
     @Test
     public void testImmutableEnabledChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertFalse(o1.isEnabled());
         assertFalse(o1.isMutable);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o2);
         assertTrue(o2.isEnabled());
         assertFalse(o2.isMutable);
 
         configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertFalse(o3.isEnabled());
         assertFalse(o3.isMutable);
@@ -108,26 +99,26 @@
     @Test
     public void testMutableEnabledChangeHasNoEffect() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
 
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertFalse(o1.isEnabled());
         assertTrue(o1.isMutable);
 
         configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o2);
         assertFalse(o2.isEnabled());
         assertTrue(o2.isMutable);
 
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertFalse(o3.isEnabled());
         assertTrue(o3.isMutable);
@@ -136,13 +127,13 @@
     @Test
     public void testMutableEnabledToImmutableEnabled() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
             configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
             impl.updateOverlaysForUser(USER);
-            final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
+            final OverlayInfo o = impl.getOverlayInfo(IDENTIFIER, USER);
             assertNotNull(o);
             assertEquals(enabled, o.isEnabled());
             assertEquals(mutable, o.isMutable);
@@ -180,38 +171,38 @@
     @Test
     public void testMutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertEquals(0, o1.priority);
         assertFalse(o1.isEnabled());
 
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o2);
         assertEquals(1, o2.priority);
         assertFalse(o2.isEnabled());
 
         // Overlay priority changing between reboots should not affect enable state of mutable
         // overlays.
-        impl.setEnabled(OVERLAY, true, USER);
+        impl.setEnabled(IDENTIFIER, true, USER);
 
         // Reorder the overlays
         configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
         configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertEquals(1, o3.priority);
         assertTrue(o3.isEnabled());
 
-        final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o4);
         assertEquals(0, o4.priority);
         assertFalse(o4.isEnabled());
@@ -220,19 +211,19 @@
     @Test
     public void testImmutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
         configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o1);
         assertEquals(0, o1.priority);
         assertTrue(o1.isEnabled());
 
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o2);
         assertEquals(1, o2.priority);
         assertTrue(o2.isEnabled());
@@ -242,12 +233,12 @@
         configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
         impl.updateOverlaysForUser(USER);
 
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(o3);
         assertEquals(1, o3.priority);
         assertTrue(o3.isEnabled());
 
-        final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o4 = impl.getOverlayInfo(IDENTIFIER2, USER);
         assertNotNull(o4);
         assertEquals(0, o4.priority);
         assertTrue(o4.isEnabled());
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index 33dbcc0..45f82a3 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.util.Pair;
 
@@ -39,19 +40,23 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
 
     private static final String OVERLAY = "com.test.overlay";
+    private static final OverlayIdentifier IDENTIFIER = new OverlayIdentifier(OVERLAY);
     private static final String TARGET = "com.test.target";
     private static final int USER = 0;
 
     private static final String OVERLAY2 = OVERLAY + "2";
     private static final String TARGET2 = TARGET + "2";
+    private static final OverlayIdentifier IDENTIFIER2 = new OverlayIdentifier(OVERLAY2);
     private static final int USER2 = USER + 1;
 
     private static final String OVERLAY3 = OVERLAY + "3";
+    private static final OverlayIdentifier IDENTIFIER3 = new OverlayIdentifier(OVERLAY3);
     private static final int USER3 = USER2 + 1;
 
     private static final String CONFIG_SIGNATURE_REFERENCE_PKG = "com.test.ref";
@@ -60,10 +65,10 @@
 
     @Test
     public void testGetOverlayInfo() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
-        final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
         assertNotNull(oi);
         assertEquals(oi.packageName, OVERLAY);
         assertEquals(oi.targetPackageName, TARGET);
@@ -72,19 +77,19 @@
 
     @Test
     public void testGetOverlayInfosForTarget() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET), USER2);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET), USER2);
 
         final OverlayManagerServiceImpl impl = getImpl();
         final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
 
         final List<OverlayInfo> ois2 = impl.getOverlayInfosForTarget(TARGET, USER2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER2)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER2)));
 
         final List<OverlayInfo> ois3 = impl.getOverlayInfosForTarget(TARGET, USER3);
         assertNotNull(ois3);
@@ -97,10 +102,10 @@
 
     @Test
     public void testGetOverlayInfosForUser() throws Exception {
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET2), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET2), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
         final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -109,13 +114,13 @@
         final List<OverlayInfo> ois = everything.get(TARGET);
         assertNotNull(ois);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(IDENTIFIER2, USER)));
 
         final List<OverlayInfo> ois2 = everything.get(TARGET2);
         assertNotNull(ois2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(IDENTIFIER3, USER)));
 
         final Map<String, List<OverlayInfo>> everything2 = impl.getOverlaysForUser(USER2);
         assertNotNull(everything2);
@@ -124,26 +129,26 @@
 
     @Test
     public void testPriority() throws Exception {
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        installNewPackage(overlay(OVERLAY2, TARGET), USER);
-        installNewPackage(overlay(OVERLAY3, TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        installPackage(overlay(OVERLAY2, TARGET), USER);
+        installPackage(overlay(OVERLAY3, TARGET), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
-        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
-        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
-        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(IDENTIFIER2, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(IDENTIFIER3, USER);
 
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertEquals(impl.setLowestPriority(OVERLAY3, USER),
+        assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
                 Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
-        assertEquals(impl.setHighestPriority(OVERLAY3, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+        assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER),
+        assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
                 Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
     }
@@ -151,61 +156,63 @@
     @Test
     public void testOverlayInfoStateTransitions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        assertNull(impl.getOverlayInfo(OVERLAY, USER));
+        assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
 
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         final FakeDeviceState.PackageBuilder target = target(TARGET);
-        installNewPackage(target, USER);
-        assertState(STATE_DISABLED, OVERLAY, USER);
+        installPackage(target, USER);
+        assertState(STATE_DISABLED, IDENTIFIER, USER);
 
-        assertEquals(impl.setEnabled(OVERLAY, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         // target upgrades do not change the state of the overlay
         upgradePackage(target, USER);
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         uninstallPackage(TARGET, USER);
-        assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+        assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
-        installNewPackage(target, USER);
-        assertState(STATE_ENABLED, OVERLAY, USER);
+        installPackage(target, USER);
+        assertState(STATE_ENABLED, IDENTIFIER, USER);
     }
 
     @Test
     public void testOnOverlayPackageUpgraded() throws Exception {
         final FakeDeviceState.PackageBuilder target = target(TARGET);
         final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
-        installNewPackage(target, USER);
-        installNewPackage(overlay, USER);
+        installPackage(target, USER);
+        installPackage(overlay, USER);
         upgradePackage(overlay, USER);
 
         // upgrade to a version where the overlay has changed its target
         final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
-        final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair =
-                upgradePackage(overlay2, USER);
-        assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER)));
-        assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER)));
+        final Pair<Set<PackageAndUser>, Set<PackageAndUser>> pair = upgradePackage(overlay2, USER);
+        assertEquals(pair.first, Set.of(new PackageAndUser(TARGET, USER)));
+        assertEquals(
+                Set.of(new PackageAndUser(TARGET, USER),
+                        new PackageAndUser("some.other.target", USER)),
+                pair.second);
     }
 
     @Test
     public void testSetEnabledAtVariousConditions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
-                () -> impl.setEnabled(OVERLAY, true, USER));
+                () -> impl.setEnabled(IDENTIFIER, true, USER));
 
         // request succeeded, and there was a change that needs to be
         // propagated to the rest of the system
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        assertEquals(impl.setEnabled(OVERLAY, true, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET), USER);
+        assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
+                Set.of(new PackageAndUser(TARGET, USER)));
 
         // request succeeded, but nothing changed
-        assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent());
+        assertTrue(impl.setEnabled(IDENTIFIER, true, USER).isEmpty());
     }
 
     @Test
@@ -214,8 +221,8 @@
         reinitializeImpl();
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -232,8 +239,8 @@
         reinitializeImpl();
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -247,8 +254,8 @@
     @Test
     public void testConfigSignaturePolicyNoConfig() throws Exception {
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -261,8 +268,8 @@
 
     @Test
     public void testConfigSignaturePolicyNoRefPkg() throws Exception {
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -279,8 +286,8 @@
         reinitializeImpl();
 
         addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
-        installNewPackage(target(TARGET), USER);
-        installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
+        installPackage(target(TARGET), USER);
+        installPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 2c477c8..16e0329 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,11 +24,12 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayInfo.State;
 import android.content.om.OverlayableInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
+import android.os.FabricatedOverlayInfo;
+import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -37,17 +38,19 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import java.util.Set;
 
 /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
 class OverlayManagerServiceImplTestsBase {
@@ -94,12 +97,11 @@
         mConfigSignaturePackageName = packageName;
     }
 
-    void assertState(@State int expected, final String overlayPackageName, int userId) {
-        final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
+    void assertState(@State int expected, final OverlayIdentifier overlay, int userId) {
+        final OverlayInfo info = mImpl.getOverlayInfo(overlay, userId);
         if (info == null) {
-            throw new IllegalStateException("package not installed");
+            throw new IllegalStateException("overlay '" + overlay + "' not installed");
         }
-
         final String msg = String.format("expected %s but was %s:",
                 OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
         assertEquals(msg, expected, info.state);
@@ -152,17 +154,13 @@
      *
      * @throws IllegalStateException if the package is currently installed
      */
-    void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId)
+    Set<PackageAndUser> installPackage(FakeDeviceState.PackageBuilder pkg, int userId)
             throws OperationFailedException {
         if (mState.select(pkg.packageName, userId) != null) {
             throw new IllegalStateException("package " + pkg.packageName + " already installed");
         }
         mState.add(pkg, userId);
-        if (pkg.targetPackage == null) {
-            mImpl.onTargetPackageAdded(pkg.packageName, userId);
-        } else {
-            mImpl.onOverlayPackageAdded(pkg.packageName, userId);
-        }
+        return CollectionUtils.emptyIfNull(mImpl.onPackageAdded(pkg.packageName, userId));
     }
 
     /**
@@ -178,26 +176,21 @@
      *
      * @throws IllegalStateException if the package is not currently installed
      */
-    Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage(
+    Pair<Set<PackageAndUser>, Set<PackageAndUser>> upgradePackage(
             FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException {
         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
         if (replacedPackage == null) {
             throw new IllegalStateException("package " + pkg.packageName + " not installed");
         }
-        Optional<PackageAndUser> opt1 = Optional.empty();
-        if (replacedPackage.targetPackageName != null) {
-            opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
-        }
+
+        final Set<PackageAndUser> updatedPackages1 =
+                CollectionUtils.emptyIfNull(mImpl.onPackageReplacing(pkg.packageName, userId));
 
         mState.add(pkg, userId);
-        Optional<PackageAndUser> opt2;
-        if (pkg.targetPackage == null) {
-            opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId);
-        } else {
-            opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
-        }
+        final Set<PackageAndUser> updatedPackages2 =
+                CollectionUtils.emptyIfNull(mImpl.onPackageReplaced(pkg.packageName, userId));
 
-        return Pair.create(opt1, opt2);
+        return Pair.create(updatedPackages1, updatedPackages2);
     }
 
     /**
@@ -208,17 +201,13 @@
      *
      * @throws IllegalStateException if the package is not currently installed
      */
-    void uninstallPackage(String packageName, int userId) throws OperationFailedException {
+    Set<PackageAndUser> uninstallPackage(String packageName, int userId) {
         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
         if (pkg == null) {
             throw new IllegalStateException("package " + packageName+ " not installed");
         }
         mState.remove(pkg.packageName);
-        if (pkg.targetPackageName == null) {
-            mImpl.onTargetPackageRemoved(pkg.packageName, userId);
-        } else {
-            mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
-        }
+        return CollectionUtils.emptyIfNull(mImpl.onPackageRemoved(packageName, userId));
     }
 
     /** Represents the state of packages installed on a fake device. */
@@ -247,11 +236,6 @@
             }
         }
 
-        List<Package> select(int userId) {
-            return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
-                    .collect(Collectors.toList());
-        }
-
         Package select(String packageName, int userId) {
             final Package pkg = mPackages.get(packageName);
             return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
@@ -335,6 +319,21 @@
                 this.apkPath = apkPath;
                 this.certificate = certificate;
             }
+
+            @Nullable
+            private AndroidPackage getPackageForUser(int user) {
+                if (!installedUserIds.contains(user)) {
+                    return null;
+                }
+                final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
+                when(pkg.getPackageName()).thenReturn(packageName);
+                when(pkg.getBaseApkPath()).thenReturn(apkPath);
+                when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
+                when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
+                when(pkg.getOverlayTargetName()).thenReturn(targetOverlayableName);
+                when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
+                return pkg;
+            }
         }
     }
 
@@ -345,21 +344,29 @@
             mState = state;
         }
 
+        @NonNull
         @Override
-        public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
-            final FakeDeviceState.Package pkg = mState.select(packageName, userId);
-            if (pkg == null) {
-                return null;
-            }
-            final ApplicationInfo ai = new ApplicationInfo();
-            ai.sourceDir = pkg.apkPath;
-            PackageInfo pi = new PackageInfo();
-            pi.applicationInfo = ai;
-            pi.packageName = pkg.packageName;
-            pi.overlayTarget = pkg.targetPackageName;
-            pi.targetOverlayableName = pkg.targetOverlayableName;
-            pi.overlayCategory = "Fake-category-" + pkg.targetPackageName;
-            return pi;
+        public ArrayMap<String, AndroidPackage> initializeForUser(int userId) {
+            final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
+            mState.mPackages.forEach((key, value) -> {
+                final AndroidPackage pkg = value.getPackageForUser(userId);
+                if (pkg != null) {
+                    packages.put(key, pkg);
+                }
+            });
+            return packages;
+        }
+
+        @Nullable
+        @Override
+        public AndroidPackage getPackageForUser(@NonNull String packageName, int userId) {
+            final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
+            return pkgState == null ? null : pkgState.getPackageForUser(userId);
+        }
+
+        @Override
+        public boolean isInstantApp(@NonNull String packageName, int userId) {
+            return false;
         }
 
         @Override
@@ -371,14 +378,6 @@
         }
 
         @Override
-        public List<PackageInfo> getOverlayPackages(int userId) {
-            return mState.select(userId).stream()
-                    .filter(p -> p.targetPackageName != null)
-                    .map(p -> getPackageInfo(p.packageName, userId))
-                    .collect(Collectors.toList());
-        }
-
-        @Override
         public @NonNull String getConfigSignaturePackage() {
             return mConfigSignaturePackageName;
         }
@@ -421,6 +420,9 @@
     static class FakeIdmapDaemon extends IdmapDaemon {
         private final FakeDeviceState mState;
         private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
+        private final ArrayMap<String, FabricatedOverlayInfo> mFabricatedOverlays =
+                new ArrayMap<>();
+        private int mFabricatedAssetSeq = 0;
 
         FakeIdmapDaemon(FakeDeviceState state) {
             this.mState = state;
@@ -433,10 +435,10 @@
         }
 
         @Override
-        String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        String createIdmap(String targetPath, String overlayPath, String overlayName,
+                int policies, boolean enforce, int userId) {
             mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
-                    getCrc(overlayPath), targetPath, policies, enforce));
+                    getCrc(overlayPath), targetPath, overlayName, policies, enforce));
             return overlayPath;
         }
 
@@ -446,8 +448,8 @@
         }
 
         @Override
-        boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
-                int userId) {
+        boolean verifyIdmap(String targetPath, String overlayPath, String overlayName, int policies,
+                boolean enforce, int userId) {
             final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
             if (idmap == null) {
                 return false;
@@ -461,6 +463,29 @@
             return mIdmapFiles.containsKey(overlayPath);
         }
 
+        @Override
+        FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
+            final String path = Integer.toString(mFabricatedAssetSeq++);
+            final FabricatedOverlayInfo info = new FabricatedOverlayInfo();
+            info.path = path;
+            info.overlayName = overlay.overlayName;
+            info.packageName = overlay.packageName;
+            info.targetPackageName = overlay.targetPackageName;
+            info.targetOverlayable = overlay.targetOverlayable;
+            mFabricatedOverlays.put(path, info);
+            return info;
+        }
+
+        @Override
+        boolean deleteFabricatedOverlay(@NonNull String path) {
+            return mFabricatedOverlays.remove(path) != null;
+        }
+
+        @Override
+        List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
+            return new ArrayList<>(mFabricatedOverlays.values());
+        }
+
         IdmapHeader getIdmap(String overlayPath) {
             return mIdmapFiles.get(overlayPath);
         }
@@ -469,14 +494,16 @@
             private final int targetCrc;
             private final int overlayCrc;
             final String targetPath;
+            final String overlayName;
             final int policies;
             final boolean enforceOverlayable;
 
-            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
-                    boolean enforceOverlayable) {
+            private IdmapHeader(int targetCrc, int overlayCrc, String targetPath,
+                    String overlayName, int policies, boolean enforceOverlayable) {
                 this.targetCrc = targetCrc;
                 this.overlayCrc = overlayCrc;
                 this.targetPath = targetPath;
+                this.overlayName = overlayName;
                 this.policies = policies;
                 this.enforceOverlayable = enforceOverlayable;
             }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index e3e7768..0a26f27 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -19,17 +19,20 @@
 import static android.content.om.OverlayInfo.STATE_DISABLED;
 import static android.content.om.OverlayInfo.STATE_ENABLED;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
 import android.text.TextUtils;
 import android.util.TypedXmlPullParser;
 import android.util.Xml;
 
+import androidx.annotation.NonNull;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
@@ -43,67 +46,32 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerSettingsTests {
     private OverlayManagerSettings mSettings;
+    private static int USER_0 = 0;
+    private static int USER_1 = 1;
 
-    private static final OverlayInfo OVERLAY_A0 = new OverlayInfo(
-            "com.test.overlay_a",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_a-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
+            null /* overlayName */);
+    private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b",
+            null /* overlayName */);
+    private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c",
+            null /* overlayName */);
 
-    private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
-            "com.test.overlay_b",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_b-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0);
+    private static final OverlayInfo OVERLAY_B_USER0 = createInfo(OVERLAY_B, USER_0);
+    private static final OverlayInfo OVERLAY_C_USER0 = createInfo(OVERLAY_C, USER_0);
 
-    private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
-            "com.test.overlay_c",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_c-1/base.apk",
-            STATE_DISABLED,
-            0,
-            0,
-            true);
+    private static final OverlayInfo OVERLAY_A_USER1 = createInfo(OVERLAY_A, USER_1);
+    private static final OverlayInfo OVERLAY_B_USER1 = createInfo(OVERLAY_B, USER_1);
 
-    private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
-            "com.test.overlay_a",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_a-1/base.apk",
-            STATE_DISABLED,
-            1,
-            0,
-            true);
-
-    private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
-            "com.test.overlay_b",
-            "com.test.target",
-            null,
-            "some-category",
-            "/data/app/com.test.overlay_b-1/base.apk",
-            STATE_DISABLED,
-            1,
-            0,
-            true);
+    private static final String TARGET_PACKAGE = "com.test.target";
 
     @Before
     public void setUp() throws Exception {
@@ -114,124 +82,112 @@
 
     @Test
     public void testSettingsInitiallyEmpty() throws Exception {
-        final int userId = 0;
-        Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(userId);
+        final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(0 /* userId */);
         assertEquals(0, map.size());
     }
 
     @Test
     public void testBasicSetAndGet() throws Exception {
-        assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+        assertDoesNotContain(mSettings, OVERLAY_A_USER0);
 
-        insert(OVERLAY_A0);
-        assertContains(mSettings, OVERLAY_A0);
-        OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
-        assertEquals(OVERLAY_A0, oi);
+        insertSetting(OVERLAY_A_USER0);
+        assertContains(mSettings, OVERLAY_A_USER0);
+        final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A, USER_0);
+        assertEquals(OVERLAY_A_USER0, oi);
 
-        assertTrue(mSettings.remove(OVERLAY_A0.packageName, OVERLAY_A0.userId));
-        assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+        assertTrue(mSettings.remove(OVERLAY_A, USER_0));
+        assertDoesNotContain(mSettings, OVERLAY_A, USER_0);
     }
 
     @Test
     public void testGetUsers() throws Exception {
-        int[] users = mSettings.getUsers();
-        assertEquals(0, users.length);
+        assertArrayEquals(new int[]{}, mSettings.getUsers());
 
-        insert(OVERLAY_A0);
-        users = mSettings.getUsers();
-        assertEquals(1, users.length);
-        assertContains(users, OVERLAY_A0.userId);
+        insertSetting(OVERLAY_A_USER0);
+        assertArrayEquals(new int[]{USER_0}, mSettings.getUsers());
 
-        insert(OVERLAY_A1);
-        insert(OVERLAY_B1);
-        users = mSettings.getUsers();
-        assertEquals(2, users.length);
-        assertContains(users, OVERLAY_A0.userId);
-        assertContains(users, OVERLAY_A1.userId);
+        insertSetting(OVERLAY_A_USER1);
+        insertSetting(OVERLAY_B_USER1);
+        assertArrayEquals(new int[]{USER_0, USER_1}, mSettings.getUsers());
     }
 
     @Test
     public void testGetOverlaysForUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_A1);
-        insert(OVERLAY_B1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_A_USER1);
+        insertSetting(OVERLAY_B_USER0);
 
-        Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(OVERLAY_A0.userId);
-        assertEquals(1, map.keySet().size());
-        assertTrue(map.keySet().contains(OVERLAY_A0.targetPackageName));
+        final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(USER_0);
+        assertEquals(Set.of(TARGET_PACKAGE), map.keySet());
 
-        List<OverlayInfo> list = map.get(OVERLAY_A0.targetPackageName);
-        assertEquals(2, list.size());
-        assertTrue(list.contains(OVERLAY_A0));
-        assertTrue(list.contains(OVERLAY_B0));
+        // Two overlays in user 0 target the same package
+        final List<OverlayInfo> list = map.get(TARGET_PACKAGE);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), list);
 
-        // getOverlaysForUser should never return null
-        map = mSettings.getOverlaysForUser(-1);
-        assertNotNull(map);
-        assertEquals(0, map.size());
+        // No users installed for user 3
+        assertEquals(Map.<String, List<OverlayInfo>>of(), mSettings.getOverlaysForUser(3));
     }
 
     @Test
     public void testRemoveUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_A1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_A_USER1);
 
-        assertContains(mSettings, OVERLAY_A0);
-        assertContains(mSettings, OVERLAY_B0);
-        assertContains(mSettings, OVERLAY_A1);
+        assertContains(mSettings, OVERLAY_A_USER0);
+        assertContains(mSettings, OVERLAY_B_USER0);
+        assertContains(mSettings, OVERLAY_A_USER1);
 
-        mSettings.removeUser(OVERLAY_A0.userId);
+        mSettings.removeUser(USER_0);
 
-        assertDoesNotContain(mSettings, OVERLAY_A0);
-        assertDoesNotContain(mSettings, OVERLAY_B0);
-        assertContains(mSettings, OVERLAY_A1);
+        assertDoesNotContain(mSettings, OVERLAY_A_USER0);
+        assertDoesNotContain(mSettings, OVERLAY_B_USER0);
+        assertContains(mSettings, OVERLAY_A_USER1);
     }
 
     @Test
     public void testOrderOfNewlyAddedItems() throws Exception {
         // new items are appended to the list
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
         // overlays keep their positions when updated
-        mSettings.setState(OVERLAY_B0.packageName, OVERLAY_B0.userId, STATE_ENABLED);
-        OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+        mSettings.setState(OVERLAY_B, USER_0, STATE_ENABLED);
+        final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B, USER_0);
+        assertNotNull(oi);
 
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, oi, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, oi, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
     }
 
     @Test
     public void testSetPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        boolean changed = mSettings.setPriority(OVERLAY_B0.packageName, OVERLAY_C0.packageName,
-                OVERLAY_B0.userId);
-        assertTrue(changed);
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        assertTrue(mSettings.setPriority(OVERLAY_B, OVERLAY_C, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        changed =
-            mSettings.setPriority(OVERLAY_B0.packageName, "does.not.exist", OVERLAY_B0.userId);
-        assertFalse(changed);
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        // Nothing happens if the parent package cannot be found
+        assertFalse(mSettings.setPriority(OVERLAY_B, new OverlayIdentifier("does.not.exist"),
+                USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        OverlayInfo otherTarget = new OverlayInfo(
+        // An overlay should not affect the priority of overlays targeting a different package
+        final OverlayInfo otherTarget = new OverlayInfo(
                 "com.test.overlay_other",
+                null,
                 "com.test.some.other.target",
                 null,
                 "some-category",
@@ -239,45 +195,36 @@
                 STATE_DISABLED,
                 0,
                 0,
-                true);
-        insert(otherTarget);
-        changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
-                OVERLAY_A0.userId);
-        assertFalse(changed);
+                true,
+                false);
+        insertSetting(otherTarget);
+        assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
     }
 
     @Test
     public void testSetLowestPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
-        boolean changed = mSettings.setLowestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
-        assertTrue(changed);
-
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_B0, OVERLAY_A0, OVERLAY_C0);
+        assertTrue(mSettings.setLowestPriority(OVERLAY_B, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_B_USER0, OVERLAY_A_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
     }
 
     @Test
     public void testSetHighestPriority() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
-        insert(OVERLAY_C0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
+        insertSetting(OVERLAY_C_USER0);
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
+                mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
 
-        List<OverlayInfo> list =
-                mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
-
-        boolean changed = mSettings.setHighestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
-        assertTrue(changed);
-
-        list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
-        assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+        assertTrue(mSettings.setHighestPriority(OVERLAY_B, USER_0));
+        assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
+                mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0));
     }
 
     // tests: persist and restore
@@ -294,8 +241,8 @@
 
     @Test
     public void testPersistDifferentOverlaysSameUser() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B0);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER0);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -304,17 +251,17 @@
         assertEquals(1, countXmlTags(xml, "overlays"));
         assertEquals(2, countXmlTags(xml, "item"));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_A0.packageName));
+                OVERLAY_A.getPackageName()));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_B0.packageName));
+                OVERLAY_B.getPackageName()));
         assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A0.userId)));
+                    Integer.toString(USER_0)));
     }
 
     @Test
     public void testPersistSameOverlayDifferentUsers() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_A1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_A_USER1);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -323,17 +270,17 @@
         assertEquals(1, countXmlTags(xml, "overlays"));
         assertEquals(2, countXmlTags(xml, "item"));
         assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
-                    OVERLAY_A0.packageName));
+                OVERLAY_A.getPackageName()));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A0.userId)));
+                    Integer.toString(USER_0)));
         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
-                    Integer.toString(OVERLAY_A1.userId)));
+                    Integer.toString(USER_1)));
     }
 
     @Test
     public void testPersistEnabled() throws Exception {
-        insert(OVERLAY_A0);
-        mSettings.setEnabled(OVERLAY_A0.packageName, OVERLAY_A0.userId, true);
+        insertSetting(OVERLAY_A_USER0);
+        mSettings.setEnabled(OVERLAY_A, USER_0, true);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -351,7 +298,7 @@
         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
 
         mSettings.restore(is);
-        assertDoesNotContain(mSettings, "com.test.overlay", 0);
+        assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0);
     }
 
     @Test
@@ -361,6 +308,7 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
                 + "<overlays version='" + version + "'>\n"
                 + "<item packageName='com.test.overlay'\n"
+                + "      overlayName='test'\n"
                 + "      userId='1234'\n"
                 + "      targetPackageName='com.test.target'\n"
                 + "      baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
@@ -373,20 +321,22 @@
         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
 
         mSettings.restore(is);
-        OverlayInfo oi = mSettings.getOverlayInfo("com.test.overlay", 1234);
+        final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
+        OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
         assertNotNull(oi);
         assertEquals("com.test.overlay", oi.packageName);
+        assertEquals("test", oi.overlayName);
         assertEquals("com.test.target", oi.targetPackageName);
         assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
         assertEquals(1234, oi.userId);
         assertEquals(STATE_DISABLED, oi.state);
-        assertFalse(mSettings.getEnabled("com.test.overlay", 1234));
+        assertFalse(mSettings.getEnabled(identifier, 1234));
     }
 
     @Test
     public void testPersistAndRestore() throws Exception {
-        insert(OVERLAY_A0);
-        insert(OVERLAY_B1);
+        insertSetting(OVERLAY_A_USER0);
+        insertSetting(OVERLAY_B_USER1);
 
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         mSettings.persist(os);
@@ -394,11 +344,11 @@
         OverlayManagerSettings newSettings = new OverlayManagerSettings();
         newSettings.restore(is);
 
-        OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
-        assertEquals(OVERLAY_A0, a);
+        OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0);
+        assertEquals(OVERLAY_A_USER0, a);
 
-        OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B1.packageName, OVERLAY_B1.userId);
-        assertEquals(OVERLAY_B1, b);
+        OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1);
+        assertEquals(OVERLAY_B_USER1, b);
     }
 
     private int countXmlTags(InputStream in, String tagToLookFor) throws Exception {
@@ -433,43 +383,53 @@
         return count;
     }
 
-    private void insert(OverlayInfo oi) throws Exception {
-        mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
-                true, false,0, oi.category);
-        mSettings.setState(oi.packageName, oi.userId, oi.state);
-        mSettings.setEnabled(oi.packageName, oi.userId, false);
+    private void insertSetting(OverlayInfo oi) throws Exception {
+        mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
+                oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
+        mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
+        mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
     }
 
     private static void assertContains(final OverlayManagerSettings settings,
             final OverlayInfo oi) {
-        assertContains(settings, oi.packageName, oi.userId);
-    }
-
-    private static void assertContains(final OverlayManagerSettings settings,
-            final String packageName, int userId) {
         try {
-            settings.getOverlayInfo(packageName, userId);
+            settings.getOverlayInfo(oi.getOverlayIdentifier(), oi.userId);
         } catch (OverlayManagerSettings.BadKeyException e) {
-            fail(String.format("settings does not contain packageName=%s userId=%d",
-                        packageName, userId));
+            fail(String.format("settings does not contain overlay=%s userId=%d",
+                    oi.getOverlayIdentifier(), oi.userId));
         }
     }
 
     private static void assertDoesNotContain(final OverlayManagerSettings settings,
             final OverlayInfo oi) {
-        assertDoesNotContain(settings, oi.packageName, oi.userId);
+        assertDoesNotContain(settings, oi.getOverlayIdentifier(), oi.userId);
     }
 
     private static void assertDoesNotContain(final OverlayManagerSettings settings,
-            final String packageName, int userId) {
+            final OverlayIdentifier overlay, int userId) {
         try {
-            settings.getOverlayInfo(packageName, userId);
-            fail(String.format("settings contains packageName=%s userId=%d", packageName, userId));
+            settings.getOverlayInfo(overlay, userId);
+            fail(String.format("settings contains overlay=%s userId=%d", overlay, userId));
         } catch (OverlayManagerSettings.BadKeyException e) {
             // do nothing: we expect to end up here
         }
     }
 
+    private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) {
+        return new OverlayInfo(
+                identifier.getPackageName(),
+                identifier.getOverlayName(),
+                "com.test.target",
+                null,
+                "some-category",
+                "/data/app/" + identifier + "/base.apk",
+                STATE_DISABLED,
+                userId,
+                0,
+                true,
+                false);
+    }
+
     private static void assertContains(int[] haystack, int needle) {
         List<Integer> list = IntStream.of(haystack)
                 .boxed()
@@ -490,16 +450,11 @@
         }
     }
 
-    private static void assertListsAreEqual(List<OverlayInfo> list, OverlayInfo... array) {
-        List<OverlayInfo> other = Stream.of(array)
-                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
-        assertListsAreEqual(list, other);
-    }
-
-    private static void assertListsAreEqual(List<OverlayInfo> list, List<OverlayInfo> other) {
-        if (!list.equals(other)) {
+    private static void assertListsAreEqual(
+            @NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) {
+        if (!expected.equals(actual)) {
             fail(String.format("lists [%s] and [%s] differ",
-                        TextUtils.join(",", list), TextUtils.join(",", other)));
+                        TextUtils.join(",", expected), TextUtils.join(",", actual)));
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
deleted file mode 100644
index 79935c2..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/StagingManagerTest.java
+++ /dev/null
@@ -1,135 +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.
- */
-
-package com.android.server.pm;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-
-import com.android.internal.os.BackgroundThread;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.function.Predicate;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-@Presubmit
-@RunWith(JUnit4.class)
-public class StagingManagerTest {
-    @Rule
-    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
-
-    private File mTmpDir;
-    private StagingManager mStagingManager;
-
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        StorageManager storageManager = Mockito.mock(StorageManager.class);
-        Context context = Mockito.mock(Context.class);
-        when(storageManager.isCheckpointSupported()).thenReturn(true);
-        when(context.getSystemService(eq(Context.POWER_SERVICE))).thenReturn(null);
-        when(context.getSystemService(eq(Context.STORAGE_SERVICE))).thenReturn(storageManager);
-
-        mTmpDir = mTemporaryFolder.newFolder("StagingManagerTest");
-        mStagingManager = new StagingManager(context, null);
-    }
-
-    /**
-     * Tests that sessions committed later shouldn't cause earlier ones to fail the overlapping
-     * check.
-     */
-    @Test
-    public void checkNonOverlappingWithStagedSessions_laterSessionShouldNotFailEarlierOnes()
-            throws Exception {
-        // Create 2 sessions with overlapping packages
-        StagingManager.StagedSession session1 = createSession(111, "com.foo", 1);
-        StagingManager.StagedSession session2 = createSession(222, "com.foo", 2);
-
-        mStagingManager.createSession(session1);
-        mStagingManager.createSession(session2);
-        // Session1 should not fail in spite of the overlapping packages
-        mStagingManager.checkNonOverlappingWithStagedSessions(session1);
-        // Session2 should fail due to overlapping packages
-        assertThrows(PackageManagerException.class,
-                () -> mStagingManager.checkNonOverlappingWithStagedSessions(session2));
-    }
-
-    private StagingManager.StagedSession createSession(int sessionId, String packageName,
-            long committedMillis) {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        params.isStaged = true;
-
-        InstallSource installSource = InstallSource.create("testInstallInitiator",
-                "testInstallOriginator", "testInstaller", "testAttributionTag");
-
-        PackageInstallerSession session = new PackageInstallerSession(
-                /* callback */ null,
-                /* context */ null,
-                /* pm */ null,
-                /* sessionProvider */ null,
-                /* looper */ BackgroundThread.getHandler().getLooper(),
-                /* stagingManager */ null,
-                /* sessionId */ sessionId,
-                /* userId */ 456,
-                /* installerUid */ -1,
-                /* installSource */ installSource,
-                /* sessionParams */ params,
-                /* createdMillis */ 0L,
-                /* committedMillis */ committedMillis,
-                /* stageDir */ mTmpDir,
-                /* stageCid */ null,
-                /* files */ null,
-                /* checksums */ null,
-                /* prepared */ true,
-                /* committed */ true,
-                /* destroyed */ false,
-                /* sealed */ false,  // Setting to true would trigger some PM logic.
-                /* childSessionIds */ null,
-                /* parentSessionId */ -1,
-                /* isReady */ false,
-                /* isFailed */ false,
-                /* isApplied */false,
-                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.STAGED_SESSION_NO_ERROR,
-                /* stagedSessionErrorMessage */ "no error");
-
-        StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
-        doReturn(packageName).when(stagedSession).getPackageName();
-        doAnswer(invocation -> {
-            Predicate<StagingManager.StagedSession> filter = invocation.getArgument(0);
-            return filter.test(stagedSession);
-        }).when(stagedSession).sessionContains(any());
-        return stagedSession;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 0dcd608..a1b2f38 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -39,6 +39,7 @@
 import com.android.frameworks.servicestests.R;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
@@ -55,6 +56,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
 import java.util.Map;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -64,6 +67,9 @@
 public class DexMetadataHelperTest {
     private static final String APK_FILE_EXTENSION = ".apk";
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+    private static final String DEX_METADATA_PACKAGE_NAME =
+            "com.android.frameworks.servicestests.install_split";
+    private static long DEX_METADATA_VERSION_CODE = 30;
 
     @Rule
     public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -76,12 +82,46 @@
     }
 
     private File createDexMetadataFile(String apkFileName) throws IOException {
+        return createDexMetadataFile(apkFileName, /*validManifest=*/true);
+    }
+
+    private File createDexMetadataFile(String apkFileName, boolean validManifest) throws IOException
+            {
+        return createDexMetadataFile(apkFileName,DEX_METADATA_PACKAGE_NAME,
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, validManifest);
+    }
+
+    private File createDexMetadataFile(String apkFileName, String packageName, Long versionCode,
+            boolean emptyManifest, boolean validManifest) throws IOException {
         File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
                 DEX_METADATA_FILE_EXTENSION));
         try (FileOutputStream fos = new FileOutputStream(dmFile)) {
             try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
                 zipOs.putNextEntry(new ZipEntry("primary.prof"));
                 zipOs.closeEntry();
+
+                if (validManifest) {
+                    zipOs.putNextEntry(new ZipEntry("manifest.json"));
+                    if (!emptyManifest) {
+                      String manifestStr = "{";
+
+                      if (packageName != null) {
+                          manifestStr += "\"packageName\": " + "\"" + packageName + "\"";
+
+                          if (versionCode != null) {
+                            manifestStr += ", ";
+                          }
+                      }
+                      if (versionCode != null) {
+                        manifestStr += " \"versionCode\": " + versionCode;
+                      }
+
+                      manifestStr += "}";
+                      byte[] bytes = manifestStr.getBytes(StandardCharsets.UTF_8);
+                      zipOs.write(bytes, /*off=*/0, /*len=*/bytes.length);
+                    }
+                    zipOs.closeEntry();
+                }
             }
         }
         return dmFile;
@@ -96,17 +136,38 @@
         return outFile;
     }
 
+    private static void validatePackageDexMetadata(AndroidPackage pkg, boolean requireManifest)
+            throws PackageParserException {
+        Collection<String> apkToDexMetadataList =
+                AndroidPackageUtils.getPackageDexMetadata(pkg).values();
+        String packageName = pkg.getPackageName();
+        long versionCode = pkg.toAppInfoWithoutState().longVersionCode;
+        for (String dexMetadata : apkToDexMetadataList) {
+            DexMetadataHelper.validateDexMetadataFile(
+                    dexMetadata, packageName, versionCode, requireManifest);
+        }
+    }
+
+    private static void validatePackageDexMetatadataVaryingRequireManifest(ParsedPackage pkg)
+            throws PackageParserException {
+        validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+        validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+    }
+
     @Test
     public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(1, packageDexMetadata.size());
         String baseDexMetadata = packageDexMetadata.get(pkg.getBaseApkPath());
         assertNotNull(baseDexMetadata);
         assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.getBaseApkPath()));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -116,7 +177,7 @@
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
         createDexMetadataFile("install_split_feature_a.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(2, packageDexMetadata.size());
@@ -127,6 +188,9 @@
         String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
         assertNotNull(splitDexMetadata);
         assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -135,7 +199,7 @@
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_feature_a.apk");
-        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, 0 /* flags */, false);
+        ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
 
         Map<String, String> packageDexMetadata = AndroidPackageUtils.getPackageDexMetadata(pkg);
         assertEquals(1, packageDexMetadata.size());
@@ -143,6 +207,9 @@
         String splitDexMetadata = packageDexMetadata.get(pkg.getSplitCodePaths()[0]);
         assertNotNull(splitDexMetadata);
         assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.getSplitCodePaths()[0]));
+
+        // Should throw no exceptions.
+        validatePackageDexMetatadataVaryingRequireManifest(pkg);
     }
 
     @Test
@@ -151,9 +218,17 @@
         File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
         Files.createFile(invalidDmFile.toPath());
         try {
-            ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(mTmpDir, 0 /* flags */, false);
-            AndroidPackageUtils.validatePackageDexMetadata(pkg);
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+            fail("Should fail validation: empty .dm file");
         } catch (PackageParserException e) {
             assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
         }
@@ -169,9 +244,112 @@
         Files.createFile(invalidDmFile.toPath());
 
         try {
-            ParsedPackage pkg = new TestPackageParser2()
-                    .parsePackage(mTmpDir, 0 /* flags */, false);
-            AndroidPackageUtils.validatePackageDexMetadata(pkg);
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/false);
+            fail("Should fail validation: empty .dm file");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileInvalidManifest()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing manifest.json in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileEmptyManifest()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
+                /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: empty manifest.json in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileBadPackageName()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: bad package name in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileBadVersionCode()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+                /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: bad version code in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileMissingPackageName()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
+                DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing package name in the .dm archive");
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageWithDmFileMissingVersionCode()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
+                /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
+
+        try {
+            ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
+            validatePackageDexMetadata(pkg, /*requireManifest=*/true);
+            fail("Should fail validation: missing version code in the .dm archive");
         } catch (PackageParserException e) {
             assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
         }
@@ -184,7 +362,7 @@
 
         try {
             DexMetadataHelper.validateDexPaths(mTmpDir.list());
-            fail("Should fail validation");
+            fail("Should fail validation: .dm filename has no match against .apk");
         } catch (IllegalStateException e) {
             // expected.
         }
@@ -200,7 +378,7 @@
 
         try {
             DexMetadataHelper.validateDexPaths(mTmpDir.list());
-            fail("Should fail validation");
+            fail("Should fail validation: split .dm filename unmatched against .apk");
         } catch (IllegalStateException e) {
             // expected.
         }
@@ -211,7 +389,7 @@
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         final File dm = createDexMetadataFile("install_split_base.apk");
         final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
-                ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */);
+                ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, /*flags=*/0);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
@@ -228,7 +406,7 @@
         try (FileInputStream is = new FileInputStream(base)) {
             final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
                     ParseTypeImpl.forDefaultParsing().reset(), is.getFD(),
-                    base.getAbsolutePath(), /* flags */ 0);
+                    base.getAbsolutePath(), /*flags=*/0);
             if (result.isError()) {
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index 22020ad..bc84e35 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -96,7 +96,7 @@
 
         int[] reasons = new int[] {
                 PackageManagerService.REASON_FIRST_BOOT,
-                PackageManagerService.REASON_BOOT,
+                PackageManagerService.REASON_POST_BOOT,
                 PackageManagerService.REASON_INSTALL,
                 PackageManagerService.REASON_BACKGROUND_DEXOPT,
                 PackageManagerService.REASON_AB_OTA,
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
new file mode 100644
index 0000000..70d85b6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.parsing.library;
+
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link AndroidNetIpSecIkeUpdater}
+ */
+@Presubmit
+@SmallTest
+@RunWith(JUnit4.class)
+public class AndroidNetIpSecIkeUpdaterTest extends PackageSharedLibraryUpdaterTest {
+
+    @Test
+    public void otherUsesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.O)
+                .addUsesLibrary("other")
+                .addUsesOptionalLibrary("optional")
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.O)
+                .addUsesLibrary("other")
+                .addUsesOptionalLibrary("optional")
+                .hideAsParsed())
+                .hideAsFinal();
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void in_usesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void in_usesOptionalLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesOptionalLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .hideAsParsed())
+                .hideAsFinal();
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
+        checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index 09c8142..9768f17 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -165,6 +165,23 @@
         checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
     }
 
+    /**
+     * Ensures that the {@link PackageBackwardCompatibility} uses a
+     * {@link AndroidNetIpSecIkeUpdater}.
+     */
+    @Test
+    public void android_net_ipsec_ike_in_usesLibraries() {
+        ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT)
+                .addUsesLibrary("android.net.ipsec.ike")
+                .hideAsParsed());
+
+        ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME)
+                .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+    }
+
     private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
         checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance);
     }
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
index 5f64249..22c38c1 100644
--- a/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/rotationresolver/RotationResolverManagerPerUserServiceTest.java
@@ -19,15 +19,21 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.rotationresolver.RotationResolverInternal;
 import android.view.Surface;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -41,75 +47,81 @@
  */
 @SmallTest
 public class RotationResolverManagerPerUserServiceTest {
-
-    @Mock
-    Context mContext;
+    private static final String PACKAGE_NAME = "test_pkg";
+    private static final String CLASS_NAME = "test_class";
 
     @Mock
     RotationResolverInternal.RotationResolverCallbackInternal mMockCallbackInternal;
-
     @Mock
-    ComponentName mMockComponentName;
+    PackageManager mMockPackageManager;
 
+    private Context mContext;
     private CancellationSignal mCancellationSignal;
-
-    private RotationResolverManagerPerUserService mSpyService;
+    private RotationResolverManagerPerUserService mService;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
-        // setup context mock
+        // setup context.
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        doReturn(PACKAGE_NAME).when(mMockPackageManager).getRotationResolverPackageName();
+        doReturn(createTestingResolveInfo()).when(mMockPackageManager).resolveServiceAsUser(any(),
+                anyInt(), anyInt());
+        doReturn(mMockPackageManager).when(mContext).getPackageManager();
         doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
 
         // setup a spy for the RotationResolverManagerPerUserService.
         final RotationResolverManagerService mainService = new RotationResolverManagerService(
                 mContext);
-        final RotationResolverManagerPerUserService mService =
-                new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
-                        mContext.getUserId());
+        mService = new RotationResolverManagerPerUserService(mainService, /* Lock */ new Object(),
+                mContext.getUserId());
 
         mCancellationSignal = new CancellationSignal();
-        mSpyService = Mockito.spy(mService);
 
-        mSpyService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
+        this.mService.mCurrentRequest = new RemoteRotationResolverService.RotationRequest(
                 mMockCallbackInternal, Surface.ROTATION_0, Surface.ROTATION_0, "", 1000L,
                 mCancellationSignal);
 
-        mSpyService.getMaster().mIsServiceEnabled = true;
+        this.mService.getMaster().mIsServiceEnabled = true;
 
-        mSpyService.mRemoteService = new MockRemoteRotationResolverService(mContext,
-                mMockComponentName, mContext.getUserId(),
+        ComponentName componentName = new ComponentName(PACKAGE_NAME, CLASS_NAME);
+        this.mService.mRemoteService = new MockRemoteRotationResolverService(mContext,
+                componentName, mContext.getUserId(),
                 /* idleUnbindTimeoutMs */60000L,
                 /* Lock */ new Object());
     }
 
     @Test
     public void testResolveRotation_callOnSuccess() {
-        doReturn(true).when(mSpyService).isServiceAvailableLocked();
-        mSpyService.mCurrentRequest = null;
+        mService.mCurrentRequest = null;
 
         RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
                 Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
 
-        mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+        mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
                 "", 1000L, mCancellationSignal);
         verify(callbackInternal).onSuccess(anyInt());
     }
 
     @Test
     public void testResolveRotation_noCrashWhenCancelled() {
-        doReturn(true).when(mSpyService).isServiceAvailableLocked();
-
         RotationResolverInternal.RotationResolverCallbackInternal callbackInternal =
                 Mockito.mock(RotationResolverInternal.RotationResolverCallbackInternal.class);
 
         final CancellationSignal cancellationSignal = new CancellationSignal();
-        mSpyService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
+        mService.resolveRotationLocked(callbackInternal, Surface.ROTATION_0, Surface.ROTATION_0,
                 "", 1000L, cancellationSignal);
         cancellationSignal.cancel();
+    }
 
-        verify(mSpyService.mCurrentRequest).cancelInternal();
+    private ResolveInfo createTestingResolveInfo() {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = PACKAGE_NAME;
+        resolveInfo.serviceInfo.name = CLASS_NAME;
+        resolveInfo.serviceInfo.permission = Manifest.permission.BIND_ROTATION_RESOLVER_SERVICE;
+        return resolveInfo;
     }
 
     static class MockRemoteRotationResolverService extends RemoteRotationResolverService {
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 b28994c..5574836 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
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 6dcfb42..daa1b25 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -27,7 +27,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.timedetector.ExternalTimeSuggestion;
+import android.app.time.ExternalTimeSuggestion;
 import android.app.timedetector.GnssTimeSuggestion;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
index 72c40ea..014bfd2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibrator.java
@@ -23,7 +23,7 @@
 import androidx.annotation.NonNull;
 
 /** Fake implementation of {@link Vibrator} for service tests. */
-public final class FakeVibrator extends Vibrator {
+final class FakeVibrator extends Vibrator {
 
     private int mDefaultHapticFeedbackIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
     private int mDefaultNotificationIntensity = Vibrator.VIBRATION_INTENSITY_MEDIUM;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index f562c16..4634e12 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -33,7 +33,7 @@
  * Provides {@link VibratorController} with controlled vibrator hardware capabilities and
  * interactions.
  */
-public final class FakeVibratorControllerProvider {
+final class FakeVibratorControllerProvider {
 
     private static final int EFFECT_DURATION = 20;
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index e71c2f5..8c62b7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -67,9 +67,8 @@
     private static final String REASON = "some reason";
     private static final VibrationAttributes VIBRATION_ATTRIBUTES =
             new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
-    private static final VibrationEffect EFFECT = VibrationEffect.createOneShot(100, 255);
     private static final CombinedVibrationEffect SYNCED_EFFECT =
-            CombinedVibrationEffect.createSynced(EFFECT);
+            CombinedVibrationEffect.createSynced(VibrationEffect.createOneShot(100, 255));
 
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
@@ -105,6 +104,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         mInputDeviceDelegate.onInputDeviceAdded(1);
 
@@ -118,6 +118,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1));
         updateInputDevices(new int[]{1});
@@ -132,6 +133,7 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
         assertFalse(mInputDeviceDelegate.isAvailable());
 
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         updateInputDevices(new int[]{1});
 
@@ -142,6 +144,7 @@
     @Test
     public void onInputDeviceChanged_withSettingsDisabled_ignoresDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ false);
 
@@ -153,6 +156,7 @@
     @Test
     public void onInputDeviceChanged_deviceLosesVibrator_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithVibrator(1), createInputDeviceWithoutVibrator(1));
 
@@ -167,6 +171,7 @@
     @Test
     public void onInputDeviceChanged_deviceLost_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}, new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithVibrator(1), (InputDevice) null);
 
@@ -181,6 +186,7 @@
     @Test
     public void onInputDeviceChanged_deviceAddsVibrator_addsDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0], new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1), createInputDeviceWithVibrator(1));
 
@@ -195,8 +201,10 @@
     @Test
     public void onInputDeviceRemoved_removesDevice() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(
                 createInputDeviceWithoutVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
 
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -209,7 +217,9 @@
     @Test
     public void updateInputDeviceVibrators_usesFlagToLoadDeviceList() throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
 
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -223,6 +233,7 @@
     public void updateInputDeviceVibrators_withDeviceWithoutVibrator_deviceIsIgnored()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[0]);
         when(mIInputManagerMock.getInputDevice(eq(1)))
                 .thenReturn(createInputDeviceWithoutVibrator(1));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
@@ -240,14 +251,16 @@
     public void vibrateIfAvailable_withInputDevices_returnsTrueAndVibratesAllDevices()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
         assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
                 UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
-        verify(mIInputManagerMock).vibrate(eq(1), same(EFFECT), any());
-        verify(mIInputManagerMock).vibrate(eq(2), same(EFFECT), any());
+        verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
+        verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
     }
 
     @Test
@@ -261,7 +274,9 @@
     public void cancelVibrateIfAvailable_withInputDevices_returnsTrueAndStopsAllDevices()
             throws Exception {
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2});
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(2))).thenReturn(new int[]{1});
         when(mIInputManagerMock.getInputDevice(eq(2))).thenReturn(createInputDeviceWithVibrator(2));
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 3ff8e76..1b7e1ca 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -40,6 +40,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.test.TestLooper;
+import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseArray;
 
@@ -709,6 +710,7 @@
         assertEquals(Arrays.asList(6), mVibratorProviders.get(3).getAmplitudes());
     }
 
+    @LargeTest
     @Test
     public void vibrate_withWaveform_totalVibrationTimeRespected() {
         int totalDuration = 10_000; // 10s
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f0d7006..ba0a472 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.vibrator;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -73,9 +73,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.vibrator.FakeVibrator;
-import com.android.server.vibrator.FakeVibratorControllerProvider;
-import com.android.server.vibrator.VibratorController;
+import com.android.server.LocalServices;
 
 import org.junit.After;
 import org.junit.Before;
@@ -198,6 +196,10 @@
                         return mVibratorProviders.get(vibratorId)
                                 .newVibratorController(vibratorId, listener);
                     }
+
+                    @Override
+                    void addService(String name, IBinder service) {
+                    }
                 });
         service.systemReady();
         return service;
@@ -529,28 +531,15 @@
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
-        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+        when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
         VibratorManagerService service = createService();
 
-        // Prebaked vibration will play fallback waveform on input device.
-        ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
-        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
-        verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
-        assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
-
-        VibrationEffect[] effects = new VibrationEffect[]{
-                VibrationEffect.createOneShot(100, 128),
-                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
-                VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
-                        .compose(),
-        };
-
-        for (VibrationEffect effect : effects) {
-            vibrate(service, effect, ALARM_ATTRS);
-            verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
-        }
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(10, 10));
+        vibrate(service, effect, ALARM_ATTRS);
+        verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
 
         // VibrationThread will start this vibration async, so wait before checking it never played.
         assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
new file mode 100644
index 0000000..aa87958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index ec28baf..07475e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1686,6 +1686,11 @@
         }
 
         @Override
+        protected void ensureFilters(ServiceInfo si, int userId) {
+
+        }
+
+        @Override
         protected void loadDefaultsFromConfig() {
             mDefaultComponents.addAll(mDefaults);
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index be489c3..5614aa2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@
                 "disabledMessage", 0, "disabledMessageResName",
                 null, null, 0, null, 0, 0,
                 0, "iconResName", "bitmapPath", null, 0,
-                null, null);
+                null, null, 0);
         return si;
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index afcf08ef..80a046a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
+import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
+
+import static com.android.server.notification.NotificationManagerService.NotificationListeners.TAG_REQUESTED_LISTENERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -27,11 +32,14 @@
 import android.content.ComponentName;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.VersionedPackage;
+import android.os.Bundle;
 import android.service.notification.NotificationListenerFilter;
+import android.service.notification.NotificationListenerService;
 import android.util.ArraySet;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -47,8 +55,6 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 public class NotificationListenersTest extends UiServiceTestCase {
 
@@ -80,22 +86,21 @@
 
     @Test
     public void testReadExtraTag() throws Exception {
-        String xml = "<req_listeners>"
+        String xml = "<" + TAG_REQUESTED_LISTENERS+ ">"
                 + "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
                 + "<allowed types=\"7\" />"
-                + "<disallowed pkgs=\"\" />"
                 + "</listener>"
                 + "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">"
                 + "<allowed types=\"4\" />"
-                + "<disallowed pkgs=\"something\" />"
+                + "<disallowed pkg=\"pkg1\" uid=\"243\"/>"
                 + "</listener>"
-                + "</req_listeners>";
+                + "</" + TAG_REQUESTED_LISTENERS + ">";
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(xml.getBytes())), null);
         parser.nextTag();
-        mListeners.readExtraTag("req_listeners", parser);
+        mListeners.readExtraTag(TAG_REQUESTED_LISTENERS, parser);
 
         validateListenersFromXml();
     }
@@ -103,8 +108,9 @@
     @Test
     public void testWriteExtraTag() throws Exception {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -134,16 +140,18 @@
 
         assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
                 .isEqualTo(4);
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10))
                 .getDisallowedPackages())
-                .contains("something");
+                .contains(a1);
     }
 
     @Test
     public void testOnUserRemoved() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -155,58 +163,68 @@
     }
 
     @Test
-    public void testOnUserUnlocked() {
-        // one exists already, say from xml
-        NotificationListenerFilter nlf =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
-        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
-
-        // new service exists or backfilling on upgrade to S
+    public void testEnsureFilters_newServiceNoMetadata() {
         ServiceInfo si = new ServiceInfo();
-        si.permission = mListeners.getConfig().bindPermission;
+        si.packageName = "new2";
+        si.name = "comp2";
+
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isNull();
+    }
+
+    @Test
+    public void testEnsureFilters_preExisting() {
+        // one exists already, say from xml
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        NotificationListenerFilter nlf =
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
+        ServiceInfo siOld = new ServiceInfo();
+        siOld.packageName = mCn2.getPackageName();
+        siOld.name = mCn2.getClassName();
+
+        mListeners.ensureFilters(siOld, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))).isEqualTo(nlf);
+    }
+
+    @Test
+    public void testEnsureFilters_newServiceWithMetadata() {
+        ServiceInfo si = new ServiceInfo();
         si.packageName = "new";
         si.name = "comp";
-        ResolveInfo ri = new ResolveInfo();
-        ri.serviceInfo = si;
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "1,2");
 
-        // incorrect service
-        ServiceInfo si2 = new ServiceInfo();
-        ResolveInfo ri2 = new ResolveInfo();
-        ri2.serviceInfo = si2;
-        si2.packageName = "new2";
-        si2.name = "comp2";
-
-        List<ResolveInfo> ris = new ArrayList<>();
-        ris.add(ri);
-        ris.add(ri2);
-
-        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
-
-        mListeners.onUserUnlocked(0);
-
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
-                .isEqualTo(4);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
-                .getDisallowedPackages())
-                .contains("something");
+        mListeners.ensureFilters(si, 0);
 
         assertThat(mListeners.getNotificationListenerFilter(
                 Pair.create(si.getComponentName(), 0)).getTypes())
-                .isEqualTo(15);
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
-                .getDisallowedPackages())
-                .isEmpty();
+                .isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING);
+    }
 
-        assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
-                .isNull();
+    @Test
+    public void testEnsureFilters_newServiceWithEmptyMetadata() {
+        ServiceInfo si = new ServiceInfo();
+        si.packageName = "new";
+        si.name = "comp";
+        si.metaData = new Bundle();
+        si.metaData.putString(NotificationListenerService.META_DATA_DEFAULT_FILTER_TYPES, "");
 
+        mListeners.ensureFilters(si, 0);
+
+        assertThat(mListeners.getNotificationListenerFilter(
+                Pair.create(si.getComponentName(), 0)).getTypes())
+                .isEqualTo(0);
     }
 
     @Test
     public void testOnPackageChanged() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
 
@@ -224,8 +242,9 @@
     @Test
     public void testOnPackageChanged_removing() {
         NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
         NotificationListenerFilter nlf2 =
-                new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
         mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
         mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
 
@@ -239,4 +258,21 @@
                 .isEqualTo(4);
     }
 
+    @Test
+    public void testOnPackageChanged_removingDisallowedPackage() {
+        NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
+        VersionedPackage a1 = new VersionedPackage("pkg1", 243);
+        NotificationListenerFilter nlf2 =
+                new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1}));
+        mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
+        mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
+
+        String[] pkgs = new String[] {"pkg1"};
+        int[] uids = new int[] {243};
+        mListeners.onPackagesChanged(true, pkgs, uids);
+
+        assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
+                .getDisallowedPackages()).isEmpty();
+    }
+
 }
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 e8888f4..a640509 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -464,7 +464,7 @@
 
         // Setup managed services
         when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
-        when(mNlf.isPackageAllowed(anyString())).thenReturn(true);
+        when(mNlf.isPackageAllowed(any())).thenReturn(true);
         when(mNlf.isPackageAllowed(null)).thenReturn(true);
         when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
         mListener = mListeners.new ManagedServiceInfo(
@@ -7307,7 +7307,7 @@
 
     @Test
     public void testIsVisibleToListener_disallowedPackage() {
-        when(mNlf.isPackageAllowed(null)).thenReturn(false);
+        when(mNlf.isPackageAllowed(any())).thenReturn(false);
 
         StatusBarNotification sbn = mock(StatusBarNotification.class);
         when(sbn.getUserId()).thenReturn(10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 72b8439..aa1110c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -719,7 +719,7 @@
         final ActivityRecord activity = createActivityWithTask();
         assertTrue(activity.hasSavedState());
 
-        ActivityRecord.activityResumedLocked(activity.appToken);
+        ActivityRecord.activityResumedLocked(activity.appToken, false /* handleSplashScreenExit */);
         assertFalse(activity.hasSavedState());
         assertNull(activity.getSavedState());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 5597be9..2686a24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -27,6 +27,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -118,6 +119,19 @@
     }
 
     @Test
+    public void testRegisterOrganizer_ignoreUntrustedDisplay() throws RemoteException {
+        doReturn(false).when(mDisplayContent).isTrusted();
+
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        List<DisplayAreaAppearedInfo> infos = mOrganizerController
+                .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList();
+
+        assertThat(infos).isEmpty();
+        verify(organizer, never()).onDisplayAreaAppeared(any(DisplayAreaInfo.class),
+                any(SurfaceControl.class));
+    }
+
+    @Test
     public void testCreateTaskDisplayArea_topBelowRoot() {
         final String newTdaName = "testTda";
         final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
@@ -186,13 +200,20 @@
     @Test
     public void testCreateTaskDisplayArea_invalidDisplayAndRoot() {
         final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+
         assertThrows(IllegalArgumentException.class, () ->
                 mOrganizerController.createTaskDisplayArea(
                         organizer, SystemServicesTestRule.sNextDisplayId + 1, FEATURE_ROOT,
                         "testTda"));
+
         assertThrows(IllegalArgumentException.class, () ->
                 mOrganizerController.createTaskDisplayArea(
                         organizer, DEFAULT_DISPLAY, FEATURE_ROOT - 1, "testTda"));
+
+        doReturn(false).when(mDisplayContent).isTrusted();
+        assertThrows(IllegalArgumentException.class, () ->
+                mOrganizerController.createTaskDisplayArea(
+                        organizer, DEFAULT_DISPLAY, FEATURE_ROOT, "testTda"));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 89b962b..d4c956d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -43,11 +43,15 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -57,6 +61,7 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
+import android.window.IDisplayAreaOrganizer;
 
 import com.google.android.collect.Lists;
 
@@ -525,6 +530,42 @@
         assertThat(mDisplayContent.getOrientationRequestingTaskDisplayArea()).isEqualTo(tda);
     }
 
+    @Test
+    public void testDisplayContentUpdateDisplayAreaOrganizers_onDisplayAreaAppeared() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+        spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+        when(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
+                .getOrganizerByFeature(FEATURE_VENDOR_FIRST))
+                .thenReturn(mockDisplayAreaOrganizer);
+
+        mDisplayContent.addChild(displayArea, 0);
+        mDisplayContent.updateDisplayAreaOrganizers();
+
+        assertEquals(mockDisplayAreaOrganizer, displayArea.mOrganizer);
+        verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+                .onDisplayAreaAppeared(
+                        eq(mockDisplayAreaOrganizer),
+                        argThat(it -> it == displayArea && it.getSurfaceControl() != null));
+    }
+
+    @Test
+    public void testRemoveImmediately_onDisplayAreaVanished() {
+        final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+                mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+        final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
+        displayArea.mOrganizer = mockDisplayAreaOrganizer;
+        spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController);
+        mDisplayContent.addChild(displayArea, 0);
+
+        displayArea.removeImmediately();
+
+        assertNull(displayArea.mOrganizer);
+        verify(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController)
+                .onDisplayAreaVanished(mockDisplayAreaOrganizer, displayArea);
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds) {
             super(wms, ANY, "half display area");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index b4fd302..781cfec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1834,7 +1834,7 @@
         mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
-    private void performLayout(DisplayContent dc) {
+    static void performLayout(DisplayContent dc) {
         dc.setLayoutNeeded();
         dc.performLayout(true /* initial */, false /* updateImeWindows */);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 21fd04e..925b6f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -29,6 +29,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -59,6 +60,10 @@
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -66,6 +71,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
 import android.util.SparseBooleanArray;
+import android.view.Surface;
+import android.window.TaskSnapshot;
 
 import androidx.test.filters.MediumTest;
 
@@ -446,12 +453,8 @@
         doReturn(false).when(child2).isOrganized();
         mRecentTasks.add(root);
 
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-        final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0 /* callingUid */).getList();
-
         // Make sure only organized child will be appended.
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
         final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos;
         assertEquals(childrenTaskInfos.size(), 1);
         assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId);
@@ -1051,9 +1054,6 @@
 
     @Test
     public void testTaskInfo_expectNoExtras() {
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-
         final Bundle data = new Bundle();
         data.putInt("key", 100);
         final Task task1 = createTaskBuilder(".Task").build();
@@ -1063,8 +1063,7 @@
                 .build();
         mRecentTasks.add(r1.getTask());
 
-        final List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
         assertTrue(infos.size() == 1);
         for (int i = 0; i < infos.size(); i++)  {
             final Bundle extras = infos.get(i).baseIntent.getExtras();
@@ -1072,6 +1071,60 @@
         }
     }
 
+    @Test
+    public void testLastSnapshotData_snapshotSaved() {
+        final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80));
+        final Task task1 = createTaskBuilder(".Task").build();
+        task1.onSnapshotChanged(snapshot);
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+        assertTrue(lastSnapshotData.bufferSize.equals(80, 80));
+    }
+
+    @Test
+    public void testLastSnapshotData_noBuffer() {
+        final Task task1 = createTaskBuilder(".Task").build();
+        final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null);
+        task1.onSnapshotChanged(snapshot);
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertTrue(lastSnapshotData.taskSize.equals(100, 100));
+        assertNull(lastSnapshotData.bufferSize);
+    }
+
+    @Test
+    public void testLastSnapshotData_notSet() {
+        final Task task1 = createTaskBuilder(".Task").build();
+
+        mRecentTasks.add(task1);
+        final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
+        final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
+                infos.get(0).lastSnapshotData;
+        assertNull(lastSnapshotData.taskSize);
+        assertNull(lastSnapshotData.bufferSize);
+    }
+
+    private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
+        HardwareBuffer buffer = null;
+        if (bufferSize != null) {
+            buffer = mock(HardwareBuffer.class);
+            doReturn(bufferSize.x).when(buffer).getWidth();
+            doReturn(bufferSize.y).when(buffer).getHeight();
+        }
+        return new TaskSnapshot(1, new ComponentName("", ""), buffer,
+                ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+                Surface.ROTATION_0, taskSize, new Rect() /* insets */, false /* isLowResolution */,
+                true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
+                false /* isTranslucent */, false /* hasImeSurface */);
+    }
+
     /**
      * Ensures that the raw recent tasks list is in the provided order. Note that the expected tasks
      * should be ordered from least to most recent.
@@ -1084,15 +1137,19 @@
         }
     }
 
+    private List<RecentTaskInfo> getRecentTasks(int flags) {
+        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
+        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
+        return mRecentTasks.getRecentTasks(MAX_VALUE, flags, true /* getTasksAllowed */,
+                TEST_USER_0_ID, 0 /* callingUid */).getList();
+    }
+
     /**
      * Ensures that the recent tasks list is in the provided order. Note that the expected tasks
      * should be ordered from least to most recent.
      */
     private void assertGetRecentTasksOrder(int getRecentTaskFlags, Task... expectedTasks) {
-        doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt());
-        doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt());
-        List<RecentTaskInfo> infos = mRecentTasks.getRecentTasks(MAX_VALUE, getRecentTaskFlags,
-                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList();
+        List<RecentTaskInfo> infos = getRecentTasks(getRecentTaskFlags);
         assertTrue(expectedTasks.length == infos.size());
         for (int i = 0; i < infos.size(); i++)  {
             assertTrue(expectedTasks[i].mTaskId == infos.get(i).taskId);
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 cc4d4ea..e843dd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -482,8 +482,7 @@
     public void testHandleActivitySizeCompatModeChanged() {
         setUpDisplaySizeWithApp(1000, 2000);
         doReturn(true).when(mTask).isOrganized();
-        ActivityRecord activity = mActivity;
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFitted();
 
@@ -499,12 +498,12 @@
 
         // Make the activity resizable again by restarting it
         clearInvocations(mTask);
-        activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        activity.mVisibleRequested = true;
-        activity.restartProcessIfVisible();
+        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
+        mActivity.mVisibleRequested = true;
+        mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
-        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);
 
         // Expect null token when switching to non-size-compat mode activity.
         verify(mTask).onSizeCompatActivityChanged();
@@ -515,6 +514,46 @@
     }
 
     @Test
+    public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
+        setUpDisplaySizeWithApp(1000, 2000);
+        doReturn(true).when(mTask).isOrganized();
+        mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        assertFitted();
+
+        // Resize the display so that the activity exercises size-compat mode.
+        resizeDisplay(mTask.mDisplayContent, 1000, 2500);
+
+        // Expect the exact token when the activity is in size compatibility mode.
+        verify(mTask).onSizeCompatActivityChanged();
+        ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
+
+        assertEquals(mActivity.appToken, taskInfo.topActivityToken);
+        assertTrue(taskInfo.topActivityInSizeCompat);
+
+        // Create another Task to hold another size compat activity.
+        clearInvocations(mTask);
+        final Task secondTask = new TaskBuilder(mSupervisor).setDisplay(mTask.getDisplayContent())
+                .setCreateActivity(true).build();
+        final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
+        doReturn(true).when(secondTask).isOrganized();
+        secondActivity.setState(Task.ActivityState.RESUMED,
+                "testHandleActivitySizeCompatModeChanged");
+        prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Resize the display so that the activity exercises size-compat mode.
+        resizeDisplay(mTask.mDisplayContent, 1000, 3000);
+
+        // Expect the exact token when the activity is in size compatibility mode.
+        verify(secondTask).onSizeCompatActivityChanged();
+        verify(mTask, never()).onSizeCompatActivityChanged();
+        taskInfo = secondTask.getTaskInfo();
+
+        assertEquals(secondActivity.appToken, taskInfo.topActivityToken);
+        assertTrue(taskInfo.topActivityInSizeCompat);
+    }
+
+    @Test
     public void testShouldUseSizeCompatModeOnResizableTask() {
         setUpDisplaySizeWithApp(1000, 2500);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 77fca3d..9c16143 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -549,6 +549,9 @@
             public void removeStartingWindow(int taskId) { }
 
             @Override
+            public void copySplashScreenView(int taskId) { }
+
+            @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
             @Override
@@ -609,10 +612,10 @@
             public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
 
             }
-
             @Override
             public void removeStartingWindow(int taskId) { }
-
+            @Override
+            public void copySplashScreenView(int taskId) { }
             @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
@@ -685,7 +688,8 @@
 
             @Override
             public void removeStartingWindow(int taskId) { }
-
+            @Override
+            public void copySplashScreenView(int taskId) { }
             @Override
             public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { }
 
@@ -829,6 +833,8 @@
         @Override
         public void removeStartingWindow(int taskId) { }
         @Override
+        public void copySplashScreenView(int taskId) { }
+        @Override
         public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) {
             mInfo = info;
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3231f8b..8969695 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -67,11 +67,14 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.when;
 
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
 import android.view.InputWindowHandle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
@@ -559,6 +562,46 @@
         assertTrue(window.isVisibleByPolicy());
     }
 
+    @Test
+    public void testCompatOverrideScale() {
+        final float overrideScale = 2; // 0.5x on client side.
+        final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
+        spyOn(cmp);
+        doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
+        final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
+        makeWindowVisible(w);
+        w.setRequestedSize(100, 200);
+        w.mAttrs.width = w.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        w.mAttrs.gravity = Gravity.TOP | Gravity.LEFT;
+        DisplayContentTests.performLayout(mDisplayContent);
+
+        // Frame on screen = 100x200. Compat frame on client = 50x100.
+        final Rect unscaledCompatFrame = new Rect(w.getWindowFrames().mCompatFrame);
+        unscaledCompatFrame.scale(overrideScale);
+        assertEquals(w.getWindowFrames().mFrame, unscaledCompatFrame);
+
+        // Surface should apply the scale.
+        w.prepareSurfaces();
+        verify(w.getPendingTransaction()).setMatrix(w.getSurfaceControl(),
+                overrideScale, 0, 0, overrideScale);
+
+        // According to "dp * density / 160 = px", density is scaled and the size in dp is the same.
+        final CompatibilityInfo compatInfo = cmp.compatibilityInfoForPackageLocked(
+                mContext.getApplicationInfo());
+        final Configuration winConfig = w.getConfiguration();
+        final Configuration clientConfig = new Configuration(w.getConfiguration());
+        compatInfo.applyToConfiguration(clientConfig.densityDpi, clientConfig);
+
+        assertEquals(winConfig.screenWidthDp, clientConfig.screenWidthDp);
+        assertEquals(winConfig.screenHeightDp, clientConfig.screenHeightDp);
+        assertEquals(winConfig.smallestScreenWidthDp, clientConfig.smallestScreenWidthDp);
+        assertEquals(winConfig.densityDpi, (int) (clientConfig.densityDpi * overrideScale));
+
+        final Rect unscaledClientBounds = new Rect(clientConfig.windowConfiguration.getBounds());
+        unscaledClientBounds.scale(overrideScale);
+        assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
+    }
+
     @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
     @Test
     public void testRequestDrawIfNeeded() {
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 eb6c6ed..c13d6b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -337,6 +337,7 @@
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
         attrs.setTitle(name);
+        attrs.packageName = "test";
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
                 OP_NONE, attrs, VISIBLE, ownerId, userId,
@@ -1143,6 +1144,9 @@
         public void removeStartingWindow(int taskId) {
         }
         @Override
+        public void copySplashScreenView(int taskId) {
+        }
+        @Override
         public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
         }
         @Override
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index aa8bbde..89e5d5f 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -19,6 +19,7 @@
         "android.hardware.usb-V1.0-java",
         "android.hardware.usb-V1.1-java",
         "android.hardware.usb-V1.2-java",
+        "android.hardware.usb-V1.3-java",
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.usb.gadget-V1.1-java",
         "android.hardware.usb.gadget-V1.2-java",
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index 8ee72b5..60172a3 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,6 +1,5 @@
 badhri@google.com
 elaurent@google.com
-moltmann@google.com
 albertccwang@google.com
 jameswei@google.com
 howardyen@google.com
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index ca18c57..6bf6715 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -42,13 +42,13 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.V1_0.IUsb;
 import android.hardware.usb.V1_0.PortRole;
 import android.hardware.usb.V1_0.PortRoleType;
 import android.hardware.usb.V1_0.Status;
 import android.hardware.usb.V1_1.PortStatus_1_1;
 import android.hardware.usb.V1_2.IUsbCallback;
 import android.hardware.usb.V1_2.PortStatus;
+import android.hardware.usb.V1_3.IUsb;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.os.Bundle;
@@ -156,6 +156,9 @@
      */
     private int mIsPortContaminatedNotificationId;
 
+    private boolean mEnableUsbDataSignaling;
+    protected int mCurrentUsbHalVersion;
+
     public UsbPortManager(Context context) {
         mContext = context;
         try {
@@ -181,6 +184,7 @@
         if (mProxy != null) {
             try {
                 mProxy.queryPortStatus();
+                mEnableUsbDataSignaling = true;
             } catch (RemoteException e) {
                 logAndPrintException(null,
                         "ServiceStart: Failed to query port status", e);
@@ -346,6 +350,66 @@
         }
     }
 
+    /**
+     * Enable/disable the USB data signaling
+     *
+     * @param enable enable or disable USB data signaling
+     */
+    public boolean enableUsbDataSignal(boolean enable) {
+        try {
+            mEnableUsbDataSignaling = enable;
+            // Call into the hal. Use the castFrom method from HIDL.
+            android.hardware.usb.V1_3.IUsb proxy = android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
+            return proxy.enableUsbDataSignal(enable);
+        } catch (RemoteException e) {
+            logAndPrintException(null, "Failed to set USB data signaling", e);
+            return false;
+        } catch (ClassCastException e) {
+            logAndPrintException(null, "Method only applicable to V1.3 or above implementation", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get USB HAL version
+     *
+     * @param none
+     */
+    public int getUsbHalVersion() {
+        return mCurrentUsbHalVersion;
+    }
+
+    /**
+     * update USB HAL version
+     *
+     * @param none
+     */
+    private void updateUsbHalVersion() {
+        android.hardware.usb.V1_3.IUsb usbProxy_V1_3 =
+                android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
+        if (usbProxy_V1_3 != null) {
+            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_3;
+            return;
+        }
+
+        android.hardware.usb.V1_2.IUsb usbProxy_V1_2 =
+                android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
+        if (usbProxy_V1_2 != null) {
+            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_2;
+            return;
+        }
+
+        android.hardware.usb.V1_1.IUsb usbProxy_V1_1 =
+                android.hardware.usb.V1_1.IUsb.castFrom(mProxy);
+        if (usbProxy_V1_1 != null) {
+            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_1;
+            return;
+        }
+
+        mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0;
+        return;
+    }
+
     public void setPortRoles(String portId, int newPowerRole, int newDataRole,
             IndentingPrintWriter pw) {
         synchronized (mLock) {
@@ -610,6 +674,9 @@
             for (PortInfo portInfo : mPorts.values()) {
                 portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS);
             }
+
+            dump.write("enable_usb_data_signaling", UsbPortManagerProto.ENABLE_USB_DATA_SIGNALING,
+                    mEnableUsbDataSignaling);
         }
 
         dump.end(token);
@@ -783,6 +850,7 @@
                 mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
                 mProxy.setCallback(mHALCallback);
                 mProxy.queryPortStatus();
+                mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0;
             } catch (NoSuchElementException e) {
                 logAndPrintException(pw, "connectToProxy: usb hal service not found."
                         + " Did the service fail to start?", e);
@@ -1115,6 +1183,7 @@
                 case MSG_SYSTEM_READY: {
                     mNotificationManager = (NotificationManager)
                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                    updateUsbHalVersion();
                     break;
                 }
             }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index edd4a38..9a13d76 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -745,6 +745,38 @@
     }
 
     @Override
+    public int getUsbHalVersion() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mPortManager != null) {
+                return mPortManager.getUsbHalVersion();
+            } else {
+                return UsbManager.USB_HAL_NOT_SUPPORTED;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public boolean enableUsbDataSignal(boolean enable) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mPortManager != null) {
+                return mPortManager.enableUsbDataSignal(enable);
+            } else {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         synchronized (mLock) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index be36f82..f687e4b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -581,17 +581,24 @@
             }
         }
 
-        VoiceInteractionServiceInfo findAvailInteractor(int userHandle, String packageName) {
-            List<ResolveInfo> available =
-                    mContext.getPackageManager().queryIntentServicesAsUser(
-                            new Intent(VoiceInteractionService.SERVICE_INTERFACE)
-                                    .setPackage(packageName),
-                            PackageManager.GET_META_DATA
-                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+        private List<ResolveInfo> queryInteractorServices(
+                @UserIdInt int user,
+                @Nullable String packageName) {
+            return mContext.getPackageManager().queryIntentServicesAsUser(
+                    new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(packageName),
+                    PackageManager.GET_META_DATA
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                    user);
+        }
+
+        VoiceInteractionServiceInfo findAvailInteractor(
+                @UserIdInt int user,
+                @Nullable String packageName) {
+            List<ResolveInfo> available = queryInteractorServices(user, packageName);
             int numAvailable = available.size();
             if (numAvailable == 0) {
-                Slog.w(TAG, "no available voice interaction services found for user " + userHandle);
+                Slog.w(TAG, "no available voice interaction services found for user " + user);
                 return null;
             }
             // Find first system package.  We never want to allow third party services to
@@ -1643,13 +1650,7 @@
                     String pkg = roleHolders.get(0);
 
                     // Try to set role holder as VoiceInteractionService
-                    List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
-                            new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
-                            PackageManager.GET_META_DATA
-                                    | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-
-                    for (ResolveInfo resolveInfo : services) {
+                    for (ResolveInfo resolveInfo : queryInteractorServices(userId, pkg)) {
                         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
 
                         VoiceInteractionServiceInfo voiceInteractionServiceInfo =
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3b06fd3..170ed3e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2024,7 +2024,7 @@
         boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
                 TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
         boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean(
-                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false);
+                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, "
                         + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, "
                         + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming,
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 99f2e5e..c6757fb 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -646,7 +646,7 @@
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED})
     public @interface OverrideNetworkType {}
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ac584c1..3f6162d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4748,6 +4748,21 @@
     public static final String KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL =
             "network_temp_not_metered_supported_bool";
 
+    /*
+     * Boolean indicating whether the SIM PIN can be stored and verified
+     * seamlessly after an unattended reboot.
+     *
+     * The device configuration value {@code config_allow_pin_storage_for_unattended_reboot}
+     * ultimately controls whether this carrier configuration option is used.  Where
+     * {@code config_allow_pin_storage_for_unattended_reboot} is false, the value of the
+     * {@link #KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL} carrier configuration option is
+     * ignored.
+     *
+     * @hide
+     */
+    public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL =
+            "store_sim_pin_for_unattended_reboot_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5304,6 +5319,7 @@
         sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
         sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
         sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
+        sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true);
     }
 
     /**
@@ -5335,11 +5351,28 @@
         public static final String KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED =
                 KEY_PREFIX + "suggestion_ssid_list_with_mac_randomization_disabled";
 
+        /**
+         * Avoid SoftAp in 5GHz if cellular is on unlicensed 5Ghz using License Assisted Access
+         * (LAA).
+         */
+        public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL =
+                KEY_PREFIX + "avoid_5ghz_softap_for_laa_bool";
+
+        /**
+         * Avoid Wifi Direct in 5GHz if cellular is on unlicensed 5Ghz using License Assisted
+         * Access (LAA).
+         */
+        public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL =
+                KEY_PREFIX + "avoid_5ghz_wifi_direct_for_laa_bool";
+
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putInt(KEY_HOTSPOT_MAX_CLIENT_COUNT, 0);
             defaults.putStringArray(KEY_SUGGESTION_SSID_LIST_WITH_MAC_RANDOMIZATION_DISABLED,
                     new String[0]);
+            defaults.putBoolean(KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL, false);
+            defaults.putBoolean(KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL, false);
 
             return defaults;
         }
diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java
deleted file mode 100644
index 7c7eb9f..0000000
--- a/telephony/java/android/telephony/RadioInterfaceCapabilities.java
+++ /dev/null
@@ -1,53 +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.
- */
-
-package android.telephony;
-
-import android.util.ArraySet;
-
-/**
- * Contains the set of supported capabilities that the Radio Interface supports on this device.
- *
- * @hide
- */
-public class RadioInterfaceCapabilities {
-
-    private final ArraySet<String> mSupportedCapabilities;
-
-
-    public RadioInterfaceCapabilities() {
-        mSupportedCapabilities = new ArraySet<>();
-    }
-
-    /**
-     * Marks a capability as supported
-     *
-     * @param capabilityName the name of the capability
-     */
-    public void addSupportedCapability(
-            @TelephonyManager.RadioInterfaceCapability String capabilityName) {
-        mSupportedCapabilities.add(capabilityName);
-    }
-
-    /**
-     * Whether the capability is supported
-     *
-     * @param capabilityName the name of the capability
-     */
-    public boolean isSupported(String capabilityName) {
-        return mSupportedCapabilities.contains(capabilityName);
-    }
-}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 1fcb504..5b5570b 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -66,9 +66,26 @@
      * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
      * capability or is currently connected to the secondary
      * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+     * @deprecated Use{@link #OVERRIDE_NETWORK_TYPE_NR_ADVANCED} instead.
      */
+    @Deprecated
     public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
 
+    /**
+     * Override network type when the device is connected NR cellular network and the data rate is
+     * higher than the generic 5G date rate.
+     * Including but not limited to
+     * <ul>
+     *   <li>The device is connected to the NR cellular network on millimeter wave bands. </li>
+     *   <li>The device is connected to the specific network which the carrier is using
+     *   proprietary means to provide a faster overall data connection than would be otherwise
+     *   possible. This may include using other bands unique to the carrier, or carrier
+     *   aggregation, for example.</li>
+     * </ul>
+     * One of the use case is that UX can show a different icon, for example, "5G+"
+     */
+    public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+
     @NetworkType
     private final  int mNetworkType;
 
@@ -169,7 +186,7 @@
             case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
             case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
             case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
-            case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+            case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
             default: return "UNKNOWN";
         }
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 65b8de2..ee3a0ef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8491,6 +8491,11 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param subId the id of the subscription to set the preferred network type for.
      * @param networkType the preferred network type
@@ -8524,6 +8529,11 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param networkTypeBitmask The bitmask of preferred network types.
      * @return true on success; false on any failure.
@@ -8550,6 +8560,11 @@
      * Set the allowed network types of the device. This is for carrier or privileged apps to
      * enable/disable certain network types on the device. The user preferred network types should
      * be set through {@link #setPreferredNetworkTypeBitmask}.
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param allowedNetworkTypes The bitmask of allowed network types.
      * @return true on success; false on any failure.
@@ -8624,6 +8639,11 @@
      * </ol>
      * This API will result in allowing an intersection of allowed network types for all reasons,
      * including the configuration done through other reasons.
+     * <p>
+     * If {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_ALLOWED_NETWORK_TYPES_USED}) returns true, then
+     * setAllowedNetworkTypesBitmap is used on the radio interface.  Otherwise,
+     * setPreferredNetworkTypesBitmap is used instead.
      *
      * @param reason the reason the allowed network type change is taking place
      * @param allowedNetworkTypes The bitmask of allowed network types.
@@ -11255,16 +11275,21 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
-     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     * Additionally, depending on the level of location permissions the caller holds (i.e. no
+     * location permissions, {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}, or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}), location-sensitive fields will
+     * be cleared from the return value.
+     *
+     * <p>Note also that if the caller holds any sort of location permission, a usage event for the
+     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION} or
+     * {@link android.app.AppOpsManager#OPSTR_FINE_LOCATION}
+     * will be logged against the caller when calling this method.
      *
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
-    @RequiresPermission(allOf = {
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.ACCESS_COARSE_LOCATION
-    })
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public @Nullable ServiceState getServiceState() {
         return getServiceStateForSubscriber(getSubId());
     }
@@ -14856,10 +14881,24 @@
     public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE =
             "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
 
+    /**
+     * Indicates whether {@link #setPreferredNetworkType}, {@link
+     * #setPreferredNetworkTypeBitmask}, {@link #setAllowedNetworkTypes} and
+     * {@link #setAllowedNetworkTypesForReason} rely on
+     * setAllowedNetworkTypesBitmap instead of setPreferredNetworkTypesBitmap on the radio
+     * interface.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
+            "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = "CAPABILITY_", value = {
             CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
+            CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
     })
     public @interface RadioInterfaceCapability {}
 
@@ -14994,6 +15033,7 @@
         }
         return THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR;
     }
+
     /**
      * Registers a listener object to receive notification of changes
      * in specified telephony states.
@@ -15350,4 +15390,66 @@
             return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
         }
     }
+
+    /**
+     * The unattended reboot was prepared successfully.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0;
+
+    /**
+     * The unattended reboot was prepared, but the user will need to manually
+     * enter the PIN code of at least one SIM card present in the device.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1;
+
+    /**
+     * The unattended reboot was not prepared due to generic error.
+     * @hide
+     */
+    @SystemApi
+    public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"PREPARE_UNATTENDED_REBOOT_"},
+            value = {
+                    PREPARE_UNATTENDED_REBOOT_SUCCESS,
+                    PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED,
+                    PREPARE_UNATTENDED_REBOOT_ERROR
+            })
+    public @interface PrepareUnattendedRebootResult {}
+
+    /**
+     * Prepare TelephonyManager for an unattended reboot. The reboot is required to be done
+     * shortly (e.g. within 15 seconds) after the API is invoked.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#REBOOT}
+     *
+     * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+     * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+     * at least one SIM card for which the user needs to manually enter the PIN
+     * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+     * of error.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REBOOT)
+    @PrepareUnattendedRebootResult
+    public int prepareForUnattendedReboot() {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.prepareForUnattendedReboot();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Telephony#prepareForUnattendedReboot RemoteException", e);
+            e.rethrowFromSystemServer();
+        }
+        return PREPARE_UNATTENDED_REBOOT_ERROR;
+    }
 }
diff --git a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
similarity index 81%
copy from core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
copy to telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
index 14d57bf..0830ff2 100644
--- a/core/java/android/app/timedetector/ExternalTimeSuggestion.aidl
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 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,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package android.telephony.ims;
 
-parcelable ExternalTimeSuggestion;
+parcelable ImsRegistrationAttributes;
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
new file mode 100644
index 0000000..ccb3231
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -0,0 +1,252 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Contains the attributes associated with the current IMS registration.
+ */
+public final class ImsRegistrationAttributes implements Parcelable {
+
+    /**
+     * Attribute to specify if an EPDG tunnel is setup over the cellular internet APN.
+     * <p>
+     * If IMS is registered through an EPDG tunnel is setup over the cellular internet APN then this
+     * bit will be set. If IMS is registered through the IMS APN, then this bit will not be set.
+     *
+     */
+    public static final int ATTR_EPDG_OVER_CELL_INTERNET = 1 << 0;
+
+    /** @hide */
+    // Defines the underlying radio technology type that we have registered for IMS over.
+    @IntDef(prefix = "ATTR_",
+            value = {
+                    ATTR_EPDG_OVER_CELL_INTERNET,
+            },
+            flag = true)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ImsAttributeFlag {}
+
+    /**
+     * Builder for creating {@link ImsRegistrationAttributes} instances.
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private final int mRegistrationTech;
+        private Set<String> mFeatureTags = Collections.emptySet();
+
+        /**
+         * Build a new instance of {@link ImsRegistrationAttributes}.
+         *
+         * @param registrationTech The Radio Access Technology that IMS is registered on.
+         */
+        public Builder(@ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
+            mRegistrationTech = registrationTech;
+        }
+
+        /**
+         * Optional IMS feature tags included in this IMS registration.
+         * @param tags A set of Strings containing the MMTEL and RCS feature tags associated with
+         *         the IMS registration. This information is used for services such as the UCE
+         *         service to ascertain the complete IMS registration state to ensure the SIP
+         *         PUBLISH is accurate. The format of the set of feature tags must be one feature
+         *         tag key and value per entry. Each feature tag will contain the feature tag name
+         *         and string value (if applicable), even if they have the same feature tag name.
+         *         For example,
+         *         {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+         *         urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} must
+         *         be split into three feature tag entries:
+         *         {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+         *         +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+         *         +g.gsma.callcomposer}}.
+         */
+        public @NonNull Builder setFeatureTags(@NonNull Set<String> tags) {
+            if (tags == null) {
+                throw new IllegalArgumentException("feature tag set must not be null");
+            }
+            mFeatureTags = new ArraySet<>(tags);
+            return this;
+        }
+
+        /**
+         * @return A new instance created from this builder.
+         */
+        public @NonNull ImsRegistrationAttributes build() {
+            return new ImsRegistrationAttributes(mRegistrationTech,
+                    RegistrationManager.getAccessType(mRegistrationTech),
+                    getAttributeFlags(mRegistrationTech),
+                    mFeatureTags);
+        }
+
+        /**
+         * @return attribute flags from the registration technology.
+         */
+        private static int getAttributeFlags(int imsRadioTech) {
+            int attributes = 0;
+            if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+                attributes |= ATTR_EPDG_OVER_CELL_INTERNET;
+            }
+            return attributes;
+        }
+    }
+
+    private final int mRegistrationTech;
+    private final int mTransportType;
+    private final int mImsAttributeFlags;
+    private final ArrayList<String> mFeatureTags;
+
+    /**
+     * Create a new {@link ImsRegistrationAttributes} instance.
+     *
+     * @param registrationTech The technology that IMS has been registered on.
+     * @param transportType The transport type that IMS has been registered on.
+     * @param imsAttributeFlags The attributes associated with the IMS registration.
+     * @param featureTags The feature tags included in the IMS registration.
+     * @see Builder
+     * @hide
+     */
+    public ImsRegistrationAttributes(
+            @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech,
+            @AccessNetworkConstants.TransportType int transportType,
+            @ImsAttributeFlag int imsAttributeFlags,
+            @Nullable Set<String> featureTags) {
+        mRegistrationTech = registrationTech;
+        mTransportType = transportType;
+        mImsAttributeFlags = imsAttributeFlags;
+        mFeatureTags = new ArrayList<>(featureTags);
+    }
+
+    /**@hide*/
+    public ImsRegistrationAttributes(Parcel source) {
+        mRegistrationTech = source.readInt();
+        mTransportType = source.readInt();
+        mImsAttributeFlags = source.readInt();
+        mFeatureTags = new ArrayList<>();
+        source.readList(mFeatureTags, null /*classloader*/);
+    }
+
+    /**
+     * @return The Radio Access Technology that the IMS registration has been registered over.
+     * @hide
+     */
+    @SystemApi
+    public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTechnology() {
+        return mRegistrationTech;
+    }
+
+    /**
+     * @return The access network transport type that IMS has been registered over.
+     */
+    public @AccessNetworkConstants.TransportType int  getTransportType() {
+        return mTransportType;
+    }
+
+    /**
+     * @return A bit-mask containing attributes associated with the IMS registration.
+     */
+    public @ImsAttributeFlag int getAttributeFlags() {
+        return mImsAttributeFlags;
+    }
+
+    /**
+     * Gets the Set of feature tags associated with the current IMS registration, if the IMS
+     * service supports supplying this information.
+     * <p>
+     * The format of the set of feature tags will be one feature tag key and value per entry and
+     * will potentially contain MMTEL and RCS feature tags, depending the configuration of the IMS
+     * service associated with the registration indications. Each feature tag will contain the
+     * feature tag name and string value (if applicable), even if they have the same feature tag
+     * name. For example, {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg,
+     * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} will be split
+     * into three feature tag  entries:
+     * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg",
+     * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session",
+     * +g.gsma.callcomposer}}.
+     * @return The Set of feature tags associated with the current IMS registration.
+     */
+    public @NonNull Set<String> getFeatureTags() {
+        if (mFeatureTags == null) {
+            return Collections.emptySet();
+        }
+        return new ArraySet<>(mFeatureTags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mRegistrationTech);
+        dest.writeInt(mTransportType);
+        dest.writeInt(mImsAttributeFlags);
+        dest.writeList(mFeatureTags);
+    }
+
+    public static final @NonNull Creator<ImsRegistrationAttributes> CREATOR =
+            new Creator<ImsRegistrationAttributes>() {
+                @Override
+                public ImsRegistrationAttributes createFromParcel(Parcel source) {
+                    return new ImsRegistrationAttributes(source);
+                }
+
+                @Override
+                public ImsRegistrationAttributes[] newArray(int size) {
+                    return new ImsRegistrationAttributes[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ImsRegistrationAttributes that = (ImsRegistrationAttributes) o;
+        return mRegistrationTech == that.mRegistrationTech
+                && mTransportType == that.mTransportType
+                && mImsAttributeFlags == that.mImsAttributeFlags
+                && Objects.equals(mFeatureTags, that.mFeatureTags);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRegistrationTech, mTransportType, mImsAttributeFlags, mFeatureTags);
+    }
+
+    @Override
+    public String toString() {
+        return "ImsRegistrationAttributes { transportType= " + mTransportType + ", attributeFlags="
+                + mImsAttributeFlags + ", featureTags=[" + mFeatureTags + "]}";
+    }
+}
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index b430bef..c682afe 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -72,30 +72,6 @@
      */
     int REGISTRATION_STATE_REGISTERED = 2;
 
-    /**
-     * @hide
-     */
-    // Defines the underlying radio technology type that we have registered for IMS over.
-    @IntDef(prefix = "ATTR_",
-            value = {
-                    ATTR_EPDG_OVER_CELL_INTERNET,
-            },
-            flag = true)
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ImsAttributes {}
-
-    /**
-     * Attribute to specify if EPDG tunnel is setup over cellular internet.
-     * if EPDG tunnel is setup over cellular internet then this bit will be set else the same will
-     * not be set.
-     */
-    int ATTR_EPDG_OVER_CELL_INTERNET = 0x00000001;
-
-    //******************************************************************************************
-    // Next attribute value: 0x00000002
-    //******************************************************************************************
-
-
     /**@hide*/
     // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
     // and WWAN are more accurate constants.
@@ -103,7 +79,8 @@
             new HashMap<Integer, Integer>() {{
                 // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE
                 // case, since it is defined.
-                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, -1);
+                put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
+                        AccessNetworkConstants.TRANSPORT_TYPE_INVALID);
                 put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
                 put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
@@ -132,6 +109,20 @@
     }
 
     /**
+     * @param regtech The registration technology.
+     * @return The Access Network type from registration technology.
+     * @hide
+     */
+    static int getAccessType(int regtech) {
+        if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regtech)) {
+            Log.w("RegistrationManager", "getAccessType - invalid regType returned: "
+                    + regtech);
+            return AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+        }
+        return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regtech);
+    }
+
+    /**
      * Callback class for receiving IMS network Registration callback events.
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
      * @see #unregisterImsRegistrationCallback(RegistrationCallback)
@@ -149,45 +140,24 @@
             }
 
             @Override
-            public void onRegistered(int imsRadioTech) {
+            public void onRegistered(ImsRegistrationAttributes attr) {
                 if (mLocalCallback == null) return;
 
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() -> {
-                        mLocalCallback.onRegistered(getAccessType(imsRadioTech));
-                    });
-                    int attributes = 0;
-                    if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                        attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
-                                true);
-                    }
-                    final int finalattributes = attributes;
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistered(getAccessType(imsRadioTech),
-                                    finalattributes));
+                    mExecutor.execute(() -> mLocalCallback.onRegistered(attr));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
             }
 
             @Override
-            public void onRegistering(int imsRadioTech) {
+            public void onRegistering(ImsRegistrationAttributes attr) {
                 if (mLocalCallback == null) return;
 
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistering(getAccessType(imsRadioTech)));
-                    int attributes = 0;
-                    if (imsRadioTech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
-                        attributes = changeBitmask(attributes, ATTR_EPDG_OVER_CELL_INTERNET,
-                                true);
-                    }
-                    final int finalattributes = attributes;
-                    mExecutor.execute(() ->
-                            mLocalCallback.onRegistering(getAccessType(imsRadioTech),
-                                    finalattributes));
+                    mExecutor.execute(() -> mLocalCallback.onRegistering(attr));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -232,31 +202,6 @@
             private void setExecutor(Executor executor) {
                 mExecutor = executor;
             }
-
-            private static int getAccessType(int regType) {
-                if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regType)) {
-                    Log.w("RegistrationManager", "RegistrationBinder - invalid regType returned: "
-                            + regType);
-                    return -1;
-                }
-                return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regType);
-            }
-
-            /**
-             * Changes a attribute bit-mask to add or remove an attribute.
-             *
-             * @param bitmask The bit-mask.
-             * @param bitfield The bit-field to change.
-             * @param enabled Whether the bit-field should be set or removed.
-             * @return The bit-mask with the bit-field changed.
-             */
-            private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
-                if (enabled) {
-                    return bitmask | bitfield;
-                } else {
-                    return bitmask & ~bitfield;
-                }
-            }
         }
 
         private final RegistrationBinder mBinder = new RegistrationBinder(this);
@@ -265,7 +210,7 @@
          * Notifies the framework when the IMS Provider is registered to the IMS network.
          *
          * @param imsTransportType the radio access technology.
-         * @deprecated Use {@link #onRegistered(int, int)} instead.
+         * @deprecated Use {@link #onRegistered(ImsRegistrationAttributes)} instead.
          */
         @Deprecated
         public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
@@ -273,25 +218,20 @@
 
         /**
          * Notifies the framework when the IMS Provider is registered to the IMS network
-         * with corresponding attributes
+         * with corresponding attributes.
          *
-         * @param imsTransportType the radio access technology.
-         * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
-         * Possible attributes are following
-         * <ul>
-         *     <li>{@link #ATTR_EPDG_OVER_CELL_INTERNET}</li>
-         * </ul>
-         *
+         * @param attributes The attributes associated with this IMS registration.
          */
-        public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType,
-                @ImsAttributes int registrationAttributes) {
+        public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+            // Default impl to keep backwards compatibility with old implementations
+            onRegistered(attributes.getTransportType());
         }
 
         /**
          * Notifies the framework when the IMS Provider is trying to register the IMS network.
          *
          * @param imsTransportType the radio access technology.
-         * @deprecated Use {@link #onRegistering(int, int)} instead.
+         * @deprecated Use {@link #onRegistering(ImsRegistrationAttributes)} instead.
          */
         public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
         }
@@ -299,12 +239,11 @@
         /**
          * Notifies the framework when the IMS Provider is trying to register the IMS network.
          *
-         * @param imsTransportType the radio access technology.
-         * @param registrationAttributes IMS registration attributes as a bitmap of attributes.
-         * Possible attributes are following
+         * @param attributes The attributes associated with this IMS registration.
          */
-        public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType,
-                @ImsAttributes int registrationAttributes) {
+        public void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+            // Default impl to keep backwards compatibility with old implementations
+            onRegistering(attributes.getTransportType());
         }
 
         /**
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
index 749b191..179407c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
@@ -21,6 +21,7 @@
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 
 /**
  * See {@link ImsManager#RegistrationCallback} for more information.
@@ -28,8 +29,8 @@
  * {@hide}
  */
 oneway interface IImsRegistrationCallback {
-   void onRegistered(int imsRadioTech);
-   void onRegistering(int imsRadioTech);
+   void onRegistered(in ImsRegistrationAttributes attr);
+   void onRegistering(in ImsRegistrationAttributes attr);
    void onDeregistered(in ImsReasonInfo info);
    void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
    void onSubscriberAssociatedUriChanged(in Uri[] uris);
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 4f753c3..39994be 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -18,17 +18,18 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.RemoteCallbackListExt;
 import com.android.internal.util.ArrayUtils;
 
@@ -89,7 +90,10 @@
 
         @Override
         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
-            return getConnectionType();
+            synchronized (mLock) {
+                return (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE
+                        : mRegistrationAttributes.getRegistrationTechnology();
+            }
         }
 
         @Override
@@ -122,8 +126,7 @@
             new RemoteCallbackListExt<>();
     private final Object mLock = new Object();
     // Locked on mLock
-    private @ImsRegistrationTech
-    int mConnectionType = REGISTRATION_TECH_NONE;
+    private ImsRegistrationAttributes mRegistrationAttributes;
     // Locked on mLock
     private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
     // Locked on mLock, create unspecified disconnect cause.
@@ -201,18 +204,24 @@
     /**
      * Notify the framework that the device is connected to the IMS network.
      *
-     * @param imsRadioTech the radio access technology. Valid values are defined as
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
+     * @param imsRadioTech the radio access technology.
      */
     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
-        updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+    }
+
+    /**
+     * Notify the framework that the device is connected to the IMS network.
+     *
+     * @param attributes The attributes associated with the IMS registration.
+     */
+    public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onRegistered(imsRadioTech);
+                c.onRegistered(attributes);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationConnected() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback.");
             }
         });
     }
@@ -220,18 +229,24 @@
     /**
      * Notify the framework that the device is trying to connect the IMS network.
      *
-     * @param imsRadioTech the radio access technology. Valid values are defined as
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
+     * @param imsRadioTech the radio access technology.
      */
     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
-        updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
+    }
+
+    /**
+     * Notify the framework that the device is trying to connect the IMS network.
+     *
+     * @param attributes The attributes associated with the IMS registration.
+     */
+    public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
+        updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         mCallbacks.broadcastAction((c) -> {
             try {
-                c.onRegistering(imsRadioTech);
+                c.onRegistering(attributes);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationProcessing() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback.");
             }
         });
     }
@@ -260,8 +275,7 @@
             try {
                 c.onDeregistered(reasonInfo);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback.");
             }
         });
     }
@@ -281,8 +295,7 @@
             try {
                 c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
             } catch (RemoteException e) {
-                Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
-                        "callback.");
+                Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback.");
             }
         });
     }
@@ -306,14 +319,13 @@
         try {
             callback.onSubscriberAssociatedUriChanged(uris);
         } catch (RemoteException e) {
-            Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping "
-                    + "callback.");
+            Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback.");
         }
     }
 
-    private void updateToState(@ImsRegistrationTech int connType, int newState) {
+    private void updateToState(ImsRegistrationAttributes attributes, int newState) {
         synchronized (mLock) {
-            mConnectionType = connType;
+            mRegistrationAttributes = attributes;
             mRegistrationState = newState;
             mLastDisconnectCause = null;
         }
@@ -325,7 +337,7 @@
             mUrisSet = false;
             mUris = null;
 
-            updateToState(REGISTRATION_TECH_NONE,
+            updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(),
                     RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
             if (info != null) {
                 mLastDisconnectCause = info;
@@ -337,30 +349,19 @@
     }
 
     /**
-     * @return the current registration connection type. Valid values are
-     * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
-     * {@link #REGISTRATION_TECH_CROSS_SIM}.
-     * @hide
-     */
-    @VisibleForTesting
-    public final @ImsRegistrationTech int getConnectionType() {
-        synchronized (mLock) {
-            return mConnectionType;
-        }
-    }
-
-    /**
      * @param c the newly registered callback that will be updated with the current registration
      *         state.
      */
     private void updateNewCallbackWithState(IImsRegistrationCallback c)
             throws RemoteException {
         int state;
+        ImsRegistrationAttributes attributes;
         ImsReasonInfo disconnectInfo;
         boolean urisSet;
         Uri[] uris;
         synchronized (mLock) {
             state = mRegistrationState;
+            attributes = mRegistrationAttributes;
             disconnectInfo = mLastDisconnectCause;
             urisSet = mUrisSet;
             uris = mUris;
@@ -371,11 +372,11 @@
                 break;
             }
             case RegistrationManager.REGISTRATION_STATE_REGISTERING: {
-                c.onRegistering(getConnectionType());
+                c.onRegistering(attributes);
                 break;
             }
             case RegistrationManager.REGISTRATION_STATE_REGISTERED: {
-                c.onRegistered(getConnectionType());
+                c.onRegistered(attributes);
                 break;
             }
             case REGISTRATION_STATE_UNKNOWN: {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0cd17da3..1d04953 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2387,4 +2387,18 @@
      * Gets the current phone capability.
      */
     PhoneCapability getPhoneCapability();
+
+    /**
+     * Prepare TelephonyManager for an unattended reboot. The reboot is
+     * required to be done shortly after the API is invoked.
+     *
+     * Requires system privileges.
+     *
+     * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success.
+     * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains
+     * at least one SIM card for which the user needs to manually enter the PIN
+     * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
+     * of error.
+     */
+    int prepareForUnattendedReboot();
 }
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 39dc9c2..e2d2eca 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,7 +16,10 @@
     name: "ApkVerityTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
-    static_libs: ["frameworks-base-hostutils"],
+    static_libs: [
+        "block_device_writer_jar",
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests", "vts"],
     target_required: [
         "block_device_writer_module",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index 37fbc29..8f2d4bc 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -51,3 +51,9 @@
     test_suites: ["general-tests", "pts", "vts"],
     gtest: false,
 }
+
+java_library_host {
+    name: "block_device_writer_jar",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed", "junit"],
+}
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
new file mode 100644
index 0000000..5c2c15b
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.blockdevicewriter;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper for block_device_writer command.
+ *
+ * <p>To use this class, please push block_device_writer binary to /data/local/tmp.
+ * 1. In Android.bp, add:
+ * <pre>
+ *     target_required: ["block_device_writer_module"],
+ * </pre>
+ * 2. In AndroidText.xml, add:
+ * <pre>
+ *     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ *         <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ *     </target_preparer>
+ * </pre>
+ */
+public final class BlockDeviceWriter {
+    private static final String EXECUTABLE = "/data/local/tmp/block_device_writer";
+
+    /**
+     * Modifies a byte of the file directly against the backing block storage.
+     *
+     * The effect can only be observed when the page cache is read from disk again. See
+     * {@link #dropCaches} for details.
+     */
+    public static void damageFileAgainstBlockDevice(ITestDevice device, String path,
+            long offsetOfTargetingByte)
+            throws DeviceNotAvailableException {
+        assertThat(path).startsWith("/data/");
+        ITestDevice.MountPointInfo mountPoint = device.getMountPointInfo("/data");
+        ArrayList<String> args = new ArrayList<>();
+        args.add(EXECUTABLE);
+        if ("f2fs".equals(mountPoint.type)) {
+            args.add("--use-f2fs-pinning");
+        }
+        args.add(mountPoint.filesystem);
+        args.add(path);
+        args.add(Long.toString(offsetOfTargetingByte));
+        CommandResult result = device.executeShellV2Command(String.join(" ", args));
+        assertWithMessage(
+                String.format("stdout=%s\nstderr=%s", result.getStdout(), result.getStderr()))
+                .that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+    }
+
+    /**
+     * Drops file caches so that the result of {@link #damageFileAgainstBlockDevice} can be
+     * observed. If a process has an open FD or memory map of the damaged file, cache eviction won't
+     * happen and the damage cannot be observed.
+     */
+    public static void dropCaches(ITestDevice device) throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command(
+                "sync && echo 1 > /proc/sys/vm/drop_caches");
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+    }
+
+    public static void assertFileNotOpen(ITestDevice device, String path)
+            throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command("lsof " + path);
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+        assertThat(result.getStdout()).isEmpty();
+    }
+
+    /**
+     * Checks if the give offset of a file can be read.
+     * This method will return false if the file has fs-verity enabled and is damaged at the offset.
+     */
+    public static boolean canReadByte(ITestDevice device, String filePath, long offset)
+            throws DeviceNotAvailableException {
+        CommandResult result = device.executeShellV2Command(
+                "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+        return result.getStatus() == CommandStatus.SUCCESS;
+    }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index d0eb9be..ab3572b 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -24,6 +24,7 @@
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.blockdevicewriter.BlockDeviceWriter;
 import com.android.fsverity.AddFsVerityCertRule;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -334,22 +335,23 @@
         long offsetFirstByte = 0;
 
         // The first two pages should be both readable at first.
-        assertTrue(canReadByte(apkPath, offsetFirstByte));
+        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
         if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
-            assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+                    offsetFirstByte + FSVERITY_PAGE_SIZE));
         }
 
         // Damage the file directly against the block device.
         damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        dropCaches();
-        assertFalse(canReadByte(apkPath, offsetFirstByte));
+        BlockDeviceWriter.dropCaches(mDevice);
+        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetFirstByte));
         if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
             long lastByteOfTheSamePage =
                     offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
-            assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
-            assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, lastByteOfTheSamePage + 1));
         }
     }
 
@@ -362,21 +364,22 @@
         long offsetOfLastByte = apkSize - 1;
 
         // The first two pages should be both readable at first.
-        assertTrue(canReadByte(apkPath, offsetOfLastByte));
+        assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
         if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
-            assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath,
+                    offsetOfLastByte - FSVERITY_PAGE_SIZE));
         }
 
         // Damage the file directly against the block device.
         damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
 
         // Expect actual read from disk to fail but only at damaged page.
-        dropCaches();
-        assertFalse(canReadByte(apkPath, offsetOfLastByte));
+        BlockDeviceWriter.dropCaches(mDevice);
+        assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, offsetOfLastByte));
         if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
             long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
-            assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
-            assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+            assertFalse(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage));
+            assertTrue(BlockDeviceWriter.canReadByte(mDevice, apkPath, firstByteOfTheSamePage - 1));
         }
     }
 
@@ -395,8 +398,8 @@
                 // from filesystem cache. Forcing GC workarounds the problem.
                 int retry = 5;
                 for (; retry > 0; retry--) {
-                    dropCaches();
-                    if (!canReadByte(path, kTargetOffset)) {
+                    BlockDeviceWriter.dropCaches(mDevice);
+                    if (!BlockDeviceWriter.canReadByte(mDevice, path, kTargetOffset)) {
                         break;
                     }
                     try {
@@ -451,16 +454,6 @@
         return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
     }
 
-    private void dropCaches() throws DeviceNotAvailableException {
-        expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
-    }
-
-    private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
-        CommandResult result = mDevice.executeShellV2Command(
-                "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
-        return result.getStatus() == CommandStatus.SUCCESS;
-    }
-
     private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
         CommandResult result = mDevice.executeShellV2Command(cmd);
         assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index eb04f69..ac9e681 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -76,11 +76,11 @@
                 Params(enableDisable = null, targetSdk = 29, result = false),
                 Params(enableDisable = null, targetSdk = 30, result = true),
 
-                Params(enableDisable = true, targetSdk = 29, result = true),
+                Params(enableDisable = true, targetSdk = 29, result = false),
                 Params(enableDisable = true, targetSdk = 30, result = true),
 
                 Params(enableDisable = false, targetSdk = 29, result = false),
-                Params(enableDisable = false, targetSdk = 30, result = false)
+                Params(enableDisable = false, targetSdk = 30, result = true)
         )
     }
 
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 401d87a..0508125 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -125,21 +125,11 @@
     }
 
     /**
-     * Test rollbacks of staged installs involving only apks with bad update.
-     * Trigger rollback phase.
-     */
-    @Test
-    public void testBadApkOnly_Phase3_Crash() throws Exception {
-        // One more crash to trigger rollback
-        RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
-    }
-
-    /**
      * Test rollbacks of staged installs involving only apks.
      * Confirm rollback phase.
      */
     @Test
-    public void testBadApkOnly_Phase4_VerifyRollback() throws Exception {
+    public void testBadApkOnly_Phase3_VerifyRollback() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
 
@@ -447,8 +437,10 @@
                 Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
                 Rollback.from(TestApp.A, 0).to(TestApp.A1));
 
-        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
-        RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT-1 times
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+        // Sleep for a while to make sure we don't trigger rollback
+        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
     }
 
     @Test
@@ -504,6 +496,45 @@
     }
 
     @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
+    }
+
+    @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        // Trigger rollback of test app.
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(5), false);
+
+        // The final crash that causes rollback will come from the host side.
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+    }
+
+    @Test
+    public void testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getRecentlyCommittedRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2);
+        assertThat(rollback).isStaged();
+        assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
+    }
+
+    @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 1d5730f..65fb7b6c 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
@@ -153,13 +153,14 @@
         getDevice().reboot();
         runPhase("testBadApkOnly_Phase2_VerifyInstall");
 
-        // Trigger rollback and wait for reboot to happen
-        runPhase("testBadApkOnly_Phase3_Crash");
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
         waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
 
         getDevice().waitForDeviceAvailable();
 
-        runPhase("testBadApkOnly_Phase4_VerifyRollback");
+        runPhase("testBadApkOnly_Phase3_VerifyRollback");
 
         assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
         assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
@@ -304,8 +305,10 @@
         getDevice().reboot();
         // Verify apex was installed and then crash the apk
         runPhase("testRollbackApexWithApkCrashing_Phase2_Crash");
-        // Wait for crash to trigger rollback
-        waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
+        waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
         getDevice().waitForDeviceAvailable();
         // Verify rollback occurred due to crash of apk-in-apex
         runPhase("testRollbackApexWithApkCrashing_Phase3_VerifyRollback");
@@ -551,6 +554,30 @@
         });
     }
 
+    /**
+     * Tests that packages are monitored across multiple reboots.
+     */
+    @Test
+    public void testWatchdogMonitorsAcrossReboots() throws Exception {
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase1_Install");
+
+        // The first reboot will make the rollback available.
+        // Information about which packages are monitored will be persisted to a file before the
+        // second reboot, and read from disk after the second reboot.
+        getDevice().reboot();
+        getDevice().reboot();
+
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase2_VerifyInstall");
+
+        // Launch the app to crash to trigger rollback
+        startActivity(TESTAPP_A);
+        // Wait for reboot to happen
+        waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
+        getDevice().waitForDeviceAvailable();
+
+        runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
+    }
+
     private void pushTestApex() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
@@ -631,6 +658,12 @@
         }
     }
 
+    private void startActivity(String packageName) throws Exception {
+        String cmd = "am start -S -a android.intent.action.MAIN "
+                + "-c android.intent.category.LAUNCHER " + packageName;
+        getDevice().executeShellCommand(cmd);
+    }
+
     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
         String pid = "";
         String lastPid = "invalid";
diff --git a/tests/StagedInstallTest/StagedInstallInternalTest.xml b/tests/StagedInstallTest/StagedInstallInternalTest.xml
index 1b8fa67..1f22cae 100644
--- a/tests/StagedInstallTest/StagedInstallInternalTest.xml
+++ b/tests/StagedInstallTest/StagedInstallInternalTest.xml
@@ -15,7 +15,7 @@
   ~ limitations under the License.
   -->
 <configuration description="Runs the internal staged install tests">
-    <option name="test-suite-tag" value="StagedInstallTest" />
+    <option name="test-suite-tag" value="StagedInstallInternalTest" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="StagedInstallInternalTestApp.apk" />
diff --git a/tests/StagedInstallTest/TEST_MAPPING b/tests/StagedInstallTest/TEST_MAPPING
index fa2a60b..cc31f2c 100644
--- a/tests/StagedInstallTest/TEST_MAPPING
+++ b/tests/StagedInstallTest/TEST_MAPPING
@@ -1,6 +1,16 @@
 {
   "presubmit-large": [
     {
+      "name": "StagedInstallInternalTest",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
       "name": "StagedInstallInternalTest"
     }
   ]
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 2201efd..8dc53ac 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.cts.install.lib.host.InstallUtilsHost;
+import android.platform.test.annotations.LargeTest;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
@@ -201,6 +202,7 @@
 
     // Test rollback-app command waits for staged sessions to be ready
     @Test
+    @LargeTest
     public void testAdbRollbackAppWaitsForStagedReady() throws Exception {
         assumeTrue("Device does not support updating APEX",
                 mHostUtils.isApexUpdateSupported());
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index d809fe8..43a5078 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -16,8 +16,14 @@
     name: "UpdatableSystemFontTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
-    static_libs: ["frameworks-base-hostutils"],
+    static_libs: [
+        "block_device_writer_jar",
+        "frameworks-base-hostutils",
+    ],
     test_suites: ["general-tests", "vts"],
+    target_required: [
+        "block_device_writer_module",
+    ],
     data: [
         ":NotoColorEmojiTtf",
         ":UpdatableSystemFontTestCertDer",
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index efe5d70..7b919bd 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
+        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
         <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
         <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
         <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 6d161a5..e249f8a9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -21,7 +21,9 @@
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.blockdevicewriter.BlockDeviceWriter;
 import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -34,6 +36,8 @@
 import org.junit.Test;
 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;
 
@@ -126,6 +130,44 @@
                 TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
     }
 
+    @Test
+    public void reboot() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+
+        expectRemoteCommandToSucceed("stop");
+        expectRemoteCommandToSucceed("start");
+        waitUntilFontCommandIsReady();
+        String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPathAfterReboot).isEqualTo(fontPath);
+    }
+
+    @Test
+    public void reboot_clearDamagedFiles() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+        assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isTrue();
+
+        BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), fontPath, 0);
+        expectRemoteCommandToSucceed("stop");
+        // We have to make sure system_server is gone before dropping caches, because system_server
+        // process holds font memory maps and prevents cache eviction.
+        waitUntilSystemServerIsGone();
+        BlockDeviceWriter.assertFileNotOpen(getDevice(), fontPath);
+        BlockDeviceWriter.dropCaches(getDevice());
+        assertThat(BlockDeviceWriter.canReadByte(getDevice(), fontPath, 0)).isFalse();
+
+        expectRemoteCommandToSucceed("start");
+        waitUntilFontCommandIsReady();
+        String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertWithMessage("Damaged file should be deleted")
+                .that(fontPathAfterReboot).startsWith("/system");
+    }
+
     private String getFontPath(String fontFileName) throws Exception {
         // TODO: add a dedicated command for testing.
         String lines = expectRemoteCommandToSucceed("cmd font dump");
@@ -153,4 +195,39 @@
                 .that(result.getStatus())
                 .isNotEqualTo(CommandStatus.SUCCESS);
     }
+
+    private void waitUntilFontCommandIsReady() {
+        waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+            try {
+                return getDevice().executeShellV2Command("cmd font status").getStatus()
+                        == CommandStatus.SUCCESS;
+            } catch (DeviceNotAvailableException e) {
+                return false;
+            }
+        });
+    }
+
+    private void waitUntilSystemServerIsGone() {
+        waitUntil(TimeUnit.SECONDS.toMillis(30), () -> {
+            try {
+                return getDevice().executeShellV2Command("pid system_server").getStatus()
+                        == CommandStatus.FAILED;
+            } catch (DeviceNotAvailableException e) {
+                return false;
+            }
+        });
+    }
+
+    private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+        long untilMillis = System.currentTimeMillis() + timeoutMillis;
+        do {
+            if (func.get()) return;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Interrupted", e);
+            }
+        } while (System.currentTimeMillis() < untilMillis);
+        throw new AssertionError("Timed out");
+    }
 }
diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml
index 6bed5a8..4c60ccf 100644
--- a/tests/net/AndroidManifest.xml
+++ b/tests/net/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.NETWORK_FACTORY" />
     <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
+    <uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
index b2bcfeb..ad5bbf2 100644
--- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt
+++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt
@@ -54,12 +54,26 @@
             }
             .build()
 
+    private val dataFromPasspoint = CaptivePortalData.Builder()
+            .setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"),
+                    CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+            .setCaptive(true)
+            .apply {
+                if (SdkLevel.isAtLeastS()) {
+                    setVenueFriendlyName("venue friendly name")
+                }
+            }
+            .build()
+
     private fun makeBuilder() = CaptivePortalData.Builder(data)
 
     @Test
     fun testParcelUnparcel() {
-        val fieldCount = if (SdkLevel.isAtLeastS()) 8 else 7
+        val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7
         assertParcelSane(data, fieldCount)
+        assertParcelSane(dataFromPasspoint, fieldCount)
 
         assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
         assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
@@ -83,6 +97,27 @@
             assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") }
             assertNotEqualsAfterChange { it.setVenueFriendlyName(null) }
         }
+
+        assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build())
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setUserPortalUrl(
+                Uri.parse("https://tc.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint")) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/other"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) }
+        assertNotEqualsAfterChange { it.setVenueInfoUrl(
+                Uri.parse("https://venue.example.com/passpoint"),
+                CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) }
     }
 
     @Test
@@ -130,6 +165,22 @@
         assertEquals("venue friendly name", data.venueFriendlyName)
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetVenueInfoUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.venueInfoUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.venueInfoUrlSource)
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    fun testGetUserPortalUrlSource() {
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER,
+                data.userPortalUrlSource)
+        assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT,
+                dataFromPasspoint.userPortalUrlSource)
+    }
+
     private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
             CaptivePortalData.Builder(this).apply { mutator(this) }.build()
 
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index d232a507..fd29a95 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -40,7 +40,7 @@
 @SmallTest
 public class OemNetworkPreferencesTest {
 
-    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT;
+    private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
     private static final String TEST_PACKAGE = "com.google.apps.contacts";
 
     private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder();
@@ -54,7 +54,7 @@
     @Test
     public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() {
         assertThrows(NullPointerException.class,
-                () -> mBuilder.removeNetworkPreference(null));
+                () -> mBuilder.clearNetworkPreference(null));
     }
 
     @Test
@@ -129,7 +129,7 @@
 
         assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
 
-        mBuilder.removeNetworkPreference(TEST_PACKAGE);
+        mBuilder.clearNetworkPreference(TEST_PACKAGE);
         networkPreferences = mBuilder.build().getNetworkPreferences();
 
         assertFalse(networkPreferences.containsKey(TEST_PACKAGE));
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index e1da3d0..dc9e587 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -17,7 +17,6 @@
 package com.android.server;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -85,7 +84,6 @@
         final String typeName = ConnectivityManager.getNetworkTypeName(type);
         mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
         mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         mNetworkCapabilities.addTransportType(transport);
         switch (transport) {
             case TRANSPORT_ETHERNET:
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index fcfb4aa..6a09b02 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -35,6 +35,7 @@
 import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
+import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -329,6 +330,9 @@
         mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); });
         mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); });
 
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); });
+        mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); });
+
         mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); });
         mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); });
         mustFail(() -> { manager.releaseNetworkRequest(nullIntent); });
@@ -377,6 +381,13 @@
                 eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
                 eq(testPkgName), eq(testAttributionTag));
         reset(mService);
+
+        Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        manager.registerSystemDefaultNetworkCallback(callback, handler);
+        verify(mService).requestNetwork(eq(null),
+                eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
+                eq(testPkgName), eq(testAttributionTag));
+        reset(mService);
     }
 
     static Message makeMessage(NetworkRequest req, int messageType) {
diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java
index 95a7942..c548e30 100644
--- a/tests/net/java/android/net/VpnManagerTest.java
+++ b/tests/net/java/android/net/VpnManagerTest.java
@@ -49,7 +49,7 @@
     private static final String IDENTITY_STRING = "Identity";
     private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
 
-    private IConnectivityManager mMockCs;
+    private IVpnManager mMockService;
     private VpnManager mVpnManager;
     private final MockContext mMockContext =
             new MockContext() {
@@ -61,24 +61,26 @@
 
     @Before
     public void setUp() throws Exception {
-        mMockCs = mock(IConnectivityManager.class);
-        mVpnManager = new VpnManager(mMockContext, mMockCs);
+        mMockService = mock(IVpnManager.class);
+        mVpnManager = new VpnManager(mMockContext, mMockService);
     }
 
     @Test
     public void testProvisionVpnProfilePreconsented() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(true);
 
         // Expect there to be no intent returned, as consent has already been granted.
         assertNull(mVpnManager.provisionVpnProfile(profile));
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testProvisionVpnProfileNeedsConsent() throws Exception {
         final PlatformVpnProfile profile = getPlatformVpnProfile();
-        when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false);
+        when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+                .thenReturn(false);
 
         // Expect intent to be returned, as consent has not already been granted.
         final Intent intent = mVpnManager.provisionVpnProfile(profile);
@@ -88,25 +90,25 @@
                 ComponentName.unflattenFromString(
                         "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
         assertEquals(expectedComponentName, intent.getComponent());
-        verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+        verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
     }
 
     @Test
     public void testDeleteProvisionedVpnProfile() throws Exception {
         mVpnManager.deleteProvisionedVpnProfile();
-        verify(mMockCs).deleteVpnProfile(eq(PKG_NAME));
+        verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStartProvisionedVpnProfile() throws Exception {
         mVpnManager.startProvisionedVpnProfile();
-        verify(mMockCs).startVpnProfile(eq(PKG_NAME));
+        verify(mMockService).startVpnProfile(eq(PKG_NAME));
     }
 
     @Test
     public void testStopProvisionedVpnProfile() throws Exception {
         mVpnManager.stopProvisionedVpnProfile();
-        verify(mMockCs).stopVpnProfile(eq(PKG_NAME));
+        verify(mMockService).stopVpnProfile(eq(PKG_NAME));
     }
 
     private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java
new file mode 100644
index 0000000..866f38c
--- /dev/null
+++ b/tests/net/java/android/net/VpnTransportInfoTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 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.net;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTransportInfoTest {
+
+    @Test
+    public void testParceling() {
+        VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        assertParcelSane(v, 1 /* fieldCount */);
+    }
+
+    @Test
+    public void testEqualsAndHashCode() {
+        VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE);
+        VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM);
+        assertNotEquals(v1, v2);
+        assertEquals(v1, v3);
+        assertEquals(v1.hashCode(), v3.hashCode());
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bf61634..2a693eb 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -200,11 +200,13 @@
 import android.net.RouteInfo;
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
+import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
+import android.net.VpnTransportInfo;
 import android.net.metrics.IpConnectivityLog;
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
@@ -361,13 +363,21 @@
 
     private static final String INTERFACE_NAME = "interface";
 
-    private static final String TEST_VENUE_URL_NA = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/";
+    private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT =
+            "https://android.com/terms/";
+    private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER =
+            "https://example.com/terms/";
     private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/";
+    private static final String TEST_USER_PORTAL_API_URL_CAPPORT =
+            "https://android.com/user/api/capport/";
     private static final String TEST_FRIENDLY_NAME = "Network friendly name";
     private static final String TEST_REDIRECT_URL = "http://example.com/firstPath";
 
     private MockContext mServiceContext;
     private HandlerThread mCsHandlerThread;
+    private HandlerThread mVMSHandlerThread;
     private ConnectivityService.Dependencies mDeps;
     private ConnectivityService mService;
     private WrappedConnectivityManager mCm;
@@ -382,6 +392,7 @@
     private TestNetIdManager mNetIdManager;
     private QosCallbackMockHelper mQosCallbackMockHelper;
     private QosCallbackTracker mQosCallbackTracker;
+    private VpnManagerService mVpnManagerService;
 
     // State variables required to emulate NetworkPolicyManagerService behaviour.
     private int mUidRules = RULE_NONE;
@@ -1110,7 +1121,7 @@
         }
 
         @Override
-        public int getActiveAppVpnType() {
+        public int getActiveVpnType() {
             return mVpnType;
         }
 
@@ -1123,10 +1134,12 @@
         private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
                 throws Exception {
             if (mAgentRegistered) throw new IllegalStateException("already registered");
+            updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent");
             mConfig = new VpnConfig();
             setUids(uids);
             if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
             mInterface = VPN_IFNAME;
+            mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType()));
             mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp,
                     mNetworkCapabilities);
             mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
@@ -1252,24 +1265,55 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
-    private void mockVpn(int uid) {
-        synchronized (mService.mVpns) {
-            int userId = UserHandle.getUserId(uid);
-            mMockVpn = new MockVpn(userId);
-            // This has no effect unless the VPN is actually connected, because things like
-            // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN
-            // netId, and check if that network is actually connected.
-            mService.mVpns.put(userId, mMockVpn);
-        }
+    private VpnManagerService makeVpnManagerService() {
+        final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
+            public int getCallingUid() {
+                return mDeps.getCallingUid();
+            }
+
+            public HandlerThread makeHandlerThread() {
+                return mVMSHandlerThread;
+            }
+
+            public KeyStore getKeyStore() {
+                return mKeyStore;
+            }
+
+            public INetd getNetd() {
+                return mMockNetd;
+            }
+
+            public INetworkManagementService getINetworkManagementService() {
+                return mNetworkManagementService;
+            }
+        };
+        return new VpnManagerService(mServiceContext, deps);
+    }
+
+    private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        final TransportInfo ti = nc.getTransportInfo();
+        assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti,
+                ti instanceof VpnTransportInfo);
+        assertEquals(type, ((VpnTransportInfo) ti).type);
+
     }
 
     private void processBroadcastForVpn(Intent intent) {
-        // The BroadcastReceiver for this broadcast checks it is being run on the handler thread.
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(intent));
+        mServiceContext.sendBroadcast(intent);
+        HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
         waitForIdle();
     }
 
+    private void mockVpn(int uid) {
+        synchronized (mVpnManagerService.mVpns) {
+            int userId = UserHandle.getUserId(uid);
+            mMockVpn = new MockVpn(userId);
+            // Every running user always has a Vpn in the mVpns array, even if no VPN is running.
+            mVpnManagerService.mVpns.put(userId, mMockVpn);
+        }
+    }
+
     private void mockUidNetworkingBlocked() {
         doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class)
                 .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules,
@@ -1384,6 +1428,7 @@
         FakeSettingsProvider.clearSettingsProvider();
         mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
                 new FakeSettingsProvider());
+        mServiceContext.setUseRegisteredHandlers(true);
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
@@ -1393,6 +1438,7 @@
         initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
 
         mCsHandlerThread = new HandlerThread("TestConnectivityService");
+        mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
         mDeps = makeDependencies();
         returnRealCallingUid();
         mService = new ConnectivityService(mServiceContext,
@@ -1415,6 +1461,8 @@
         // getSystemService() correctly.
         mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
         mService.systemReadyInternal();
+        mVpnManagerService = makeVpnManagerService();
+        mVpnManagerService.systemReady();
         mockVpn(Process.myUid());
         mCm.bindProcessToNetwork(null);
         mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -1442,7 +1490,6 @@
         doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
         doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
         doReturn(mBatteryStatsService).when(deps).getBatteryStatsService();
-        doReturn(mKeyStore).when(deps).getKeyStore();
         doAnswer(inv -> {
             mPolicyTracker = new WrappedMultinetworkPolicyTracker(
                     inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -2707,10 +2754,6 @@
 
         NetworkCapabilities filter = new NetworkCapabilities();
         filter.addCapability(capability);
-        // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
-        // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
-        // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
-        filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
         final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
         handlerThread.start();
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -3286,39 +3329,68 @@
     }
 
     private class CaptivePortalTestData {
-        CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData,
-                CaptivePortalData expectedMergedData) {
-            mNaData = naData;
+        CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData,
+                CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData,
+                CaptivePortalData expectedMergedOtherData) {
+            mNaPasspointData = naPasspointData;
             mCapportData = capportData;
-            mExpectedMergedData = expectedMergedData;
+            mNaOtherData = naOtherData;
+            mExpectedMergedPasspointData = expectedMergedPasspointData;
+            mExpectedMergedOtherData = expectedMergedOtherData;
         }
 
-        public final CaptivePortalData mNaData;
+        public final CaptivePortalData mNaPasspointData;
         public final CaptivePortalData mCapportData;
-        public final CaptivePortalData mExpectedMergedData;
+        public final CaptivePortalData mNaOtherData;
+        public final CaptivePortalData mExpectedMergedPasspointData;
+        public final CaptivePortalData mExpectedMergedOtherData;
+
     }
 
     private CaptivePortalTestData setupCaptivePortalData() {
         final CaptivePortalData capportData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
                 .setExpiryTime(1000000L)
                 .setBytesRemaining(12345L)
                 .build();
 
-        final CaptivePortalData naData = new CaptivePortalData.Builder()
+        final CaptivePortalData naPasspointData = new CaptivePortalData.Builder()
                 .setBytesRemaining(80802L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder()
+        final CaptivePortalData naOtherData = new CaptivePortalData.Builder()
+                .setBytesRemaining(80802L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER)
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+
+        final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
                 .setBytesRemaining(12345L)
                 .setExpiryTime(1000000L)
-                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
+                .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT),
+                        CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT)
                 .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
 
-        return new CaptivePortalTestData(naData, capportData, expectedMergedData);
+        final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder()
+                .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
+                .setBytesRemaining(12345L)
+                .setExpiryTime(1000000L)
+                .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
+                .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT))
+                .setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
+        return new CaptivePortalTestData(naPasspointData, capportData, naOtherData,
+                expectedMergedPasspointData, expectedMergedOtherData);
     }
 
     @Test
@@ -3332,15 +3404,26 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
 
-        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
-        // on the bytes remaining.
+        // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm
+        // that API data gets precedence on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the capport data is merged
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData
+                        .equals(lp.getCaptivePortalData()));
+
+        // Now send this information from non-Passpoint source, confirm that Capport data takes
+        // precedence
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the capport data is merged
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData
+                        .equals(lp.getCaptivePortalData()));
 
         // Create a new LP with no Network agent capport data
         final LinkProperties newLps = new LinkProperties();
@@ -3357,12 +3440,12 @@
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
                 lp -> lp.getCaptivePortalData() == null);
 
-        newLps.setCaptivePortalData(captivePortalTestData.mNaData);
+        newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(newLps);
 
         // Make sure that only the network agent capport data is available
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
     }
 
     @Test
@@ -3373,12 +3456,12 @@
         // Venue URL and friendly name from Network agent, confirm that API data gets precedence
         // on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
         mWiFiNetworkAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the data is saved correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
 
         // Expected merged data: Network agent data is preferred, and values that are not used by
         // it are merged from capport data
@@ -3386,7 +3469,8 @@
 
         // Make sure that the Capport data is merged correctly
         captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
+                lp -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+                        lp.getCaptivePortalData()));
 
         // Now set the naData to null
         linkProperties.setCaptivePortalData(null);
@@ -3397,6 +3481,32 @@
                 lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
     }
 
+    @Test
+    public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport()
+            throws Exception {
+        final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
+        final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
+
+        // Venue URL and friendly name from Network agent, confirm that API data gets precedence
+        // on the bytes remaining.
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
+        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+
+        // Make sure that the data is saved correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData()));
+
+        // Expected merged data: Network agent data is preferred, and values that are not used by
+        // it are merged from capport data
+        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+
+        // Make sure that the Capport data is merged correctly
+        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
+                lp -> captivePortalTestData.mExpectedMergedOtherData.equals(
+                        lp.getCaptivePortalData()));
+    }
+
     private NetworkRequest.Builder newWifiRequestBuilder() {
         return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
     }
@@ -3649,10 +3759,19 @@
 
     @Test
     public void testRegisterDefaultNetworkCallback() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
         defaultNetworkCallback.assertNoCallback();
 
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler);
+        systemDefaultCallback.assertNoCallback();
+
         // Create a TRANSPORT_CELLULAR request to keep the mobile interface up
         // whenever Wi-Fi is up. Without this, the mobile network agent is
         // reaped before any other activity can take place.
@@ -3667,27 +3786,35 @@
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
         // followed by AVAILABLE cell.
@@ -3695,19 +3822,25 @@
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         mMockVpn.establishForMyUid();
         assertUidRangesUpdatedForMyUid(true);
         defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(null, systemDefaultCallback.getLastAvailableNetwork());
 
         mMockVpn.disconnect();
         defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -3773,6 +3906,24 @@
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
 
+    @Test
+    public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception {
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(false /* validated */);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        assertThrows(SecurityException.class,
+                () -> mCm.registerSystemDefaultNetworkCallback(callback, handler));
+        callback.assertNoCallback();
+
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+        mCm.registerSystemDefaultNetworkCallback(callback, handler);
+        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(callback);
+    }
+
     private void setCaptivePortalMode(int mode) {
         ContentResolver cr = mServiceContext.getContentResolver();
         Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
@@ -3962,7 +4113,6 @@
         handlerThread.start();
         NetworkCapabilities filter = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .addCapability(NET_CAPABILITY_INTERNET);
         final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                 mServiceContext, "testFactory", filter);
@@ -5866,7 +6016,6 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkDownstreamBandwidthKbps(10);
         final NetworkCapabilities wifiNc = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
@@ -5875,7 +6024,6 @@
                 .addCapability(NET_CAPABILITY_NOT_ROAMING)
                 .addCapability(NET_CAPABILITY_NOT_CONGESTED)
                 .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkUpstreamBandwidthKbps(20);
         mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
         mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
@@ -6134,6 +6282,10 @@
 
     @Test
     public void testVpnNetworkActive() throws Exception {
+        // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
+                PERMISSION_GRANTED);
+
         final int uid = Process.myUid();
 
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -6141,6 +6293,7 @@
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
         final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build();
         final NetworkRequest genericRequest = new NetworkRequest.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_VPN).build();
@@ -6154,6 +6307,8 @@
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
         mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback);
         mCm.registerDefaultNetworkCallback(defaultCallback);
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
         defaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6163,6 +6318,7 @@
         genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -6183,7 +6339,10 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiNetworkAgent.getNetwork(),
+                systemDefaultCallback.getLastAvailableNetwork());
 
         ranges.clear();
         mMockVpn.setUids(ranges);
@@ -6200,6 +6359,7 @@
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -6211,6 +6371,7 @@
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
         defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
 
         mWiFiNetworkAgent.disconnect();
 
@@ -6219,6 +6380,7 @@
         wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
 
         mMockVpn.disconnect();
 
@@ -6227,12 +6389,14 @@
         wifiNetworkCallback.assertNoCallback();
         vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
         mCm.unregisterNetworkCallback(wifiNetworkCallback);
         mCm.unregisterNetworkCallback(vpnNetworkCallback);
         mCm.unregisterNetworkCallback(defaultCallback);
+        mCm.unregisterNetworkCallback(systemDefaultCallback);
     }
 
     @Test
@@ -6368,6 +6532,8 @@
         assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED));
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
     }
 
     private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
@@ -6407,6 +6573,7 @@
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         // A VPN without underlying networks is not suspended.
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         final int userId = UserHandle.getUserId(Process.myUid());
         assertDefaultNetworkCapabilities(userId /* no networks */);
@@ -6570,6 +6737,7 @@
         // By default, VPN is set to track default network (i.e. its underlying networks is null).
         // In case of no default network, VPN is considered metered.
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Connect to Cell; Cell is the default network.
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -6627,6 +6795,7 @@
         NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertNotNull("nc=" + nc, nc.getUids());
         assertEquals(nc.getUids(), uidRangesForUid(uid));
+        assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Set an underlying network and expect to see the VPN transports change.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -6709,8 +6878,8 @@
 
         // Enable always-on VPN lockdown. The main user loses network access because no VPN is up.
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */,
-                allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
+                true /* lockdown */, allowList);
         waitForIdle();
         assertNull(mCm.getActiveNetworkForUid(uid));
         // This is arguably overspecified: a UID that is not running doesn't have an active network.
@@ -6740,7 +6909,8 @@
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
 
-        mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
+                allowList);
         waitForIdle();
     }
 
@@ -7116,7 +7286,8 @@
         final int uid = Process.myUid();
         final int userId = UserHandle.getUserId(uid);
         final ArrayList<String> allowList = new ArrayList<>();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
 
         UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
@@ -7138,7 +7309,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown, expect to see the network unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7151,7 +7322,8 @@
 
         // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
         allowList.add(TEST_PACKAGE_NAME);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
         vpnUidCallback.assertNoCallback();
@@ -7184,11 +7356,12 @@
 
         // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
         // Everything should now be blocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
         allowList.clear();
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
@@ -7201,7 +7374,7 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown. Everything is unblocked.
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7213,7 +7386,8 @@
 
         // Enable and disable an always-on VPN package without lockdown. Expect no changes.
         reset(mMockNetd);
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */,
+                allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7224,7 +7398,7 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
-        mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
         callback.assertNoCallback();
         defaultCallback.assertNoCallback();
@@ -7236,7 +7410,8 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         // Enable lockdown and connect a VPN. The VPN is not blocked.
-        mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
+                allowList);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -7282,10 +7457,14 @@
         when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
     }
 
-    private void establishLegacyLockdownVpn() throws Exception {
+    private void establishLegacyLockdownVpn(Network underlying) throws Exception {
+        // The legacy lockdown VPN only supports userId 0, and must have an underlying network.
+        assertNotNull(underlying);
+        mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY);
         // The legacy lockdown VPN only supports userId 0.
         final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
         mMockVpn.registerAgent(ranges);
+        mMockVpn.setUnderlyingNetworks(new Network[]{underlying});
         mMockVpn.connect(true);
     }
 
@@ -7293,6 +7472,9 @@
     public void testLegacyLockdownVpn() throws Exception {
         mServiceContext.setPermission(
                 Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+        // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
+        mServiceContext.setPermission(
+                Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7301,6 +7483,10 @@
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
+        final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
+        mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback,
+                new Handler(ConnectivityThread.getInstanceLooper()));
+
         // Pretend lockdown VPN was configured.
         setupLegacyLockdownVpn();
 
@@ -7330,6 +7516,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7341,6 +7528,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED,
+                mCellNetworkAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -7350,6 +7539,7 @@
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         b1.expectBroadcast();
 
         // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
@@ -7359,6 +7549,7 @@
         mCellNetworkAgent.connect(false /* validated */);
         callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
         b1.expectBroadcast();
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7381,9 +7572,10 @@
         mMockVpn.expectStartLegacyVpnRunner();
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         b1.expectBroadcast();
         b2.expectBroadcast();
@@ -7395,6 +7587,7 @@
         assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
         assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY);
 
         // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
         final LinkProperties wifiLp = new LinkProperties();
@@ -7422,11 +7615,10 @@
         // fact that a VPN is connected should only result in the VPN itself being unblocked, not
         // any other network. Bug in isUidBlockedByVpn?
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         callback.expectCallback(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI));
         defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
         defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
 
         // While the VPN is reconnecting on the new network, everything is blocked.
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
@@ -7437,9 +7629,10 @@
         // The VPN comes up again on wifi.
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn();
+        establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        systemDefaultCallback.assertNoCallback();
         b1.expectBroadcast();
         b2.expectBroadcast();
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -7453,14 +7646,10 @@
         assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Nothing much happens since it's not the default network.
-        // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
-        // NetworkInfo is updated. This is probably a bug.
-        // TODO: consider fixing this.
-        b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
         mCellNetworkAgent.disconnect();
-        b1.expectBroadcast();
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
         defaultCallback.assertNoCallback();
+        systemDefaultCallback.assertNoCallback();
 
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -7470,6 +7659,7 @@
         b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
         b1.expectBroadcast();
         callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
         b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
@@ -7521,13 +7711,19 @@
             mWiFiNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
             callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
-            reset(mMockNetd);
+            // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
+            //  it.
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+                reset(mMockNetd);
+            }
 
             mCellNetworkAgent.removeCapability(testCap);
             callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
             callbackWithoutCap.assertNoCallback();
-            verify(mMockNetd).networkClearDefault();
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkClearDefault();
+            }
 
             mCm.unregisterNetworkCallback(callbackWithCap);
             mCm.unregisterNetworkCallback(callbackWithoutCap);
@@ -8521,11 +8717,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8533,11 +8725,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
 
-        try {
-            mService.getConnectionOwnerUid(getTestConnectionInfo());
-            fail("Expected SecurityException for non-VpnService app");
-        } catch (SecurityException expected) {
-        }
+        assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 799bcc8..c86224a 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -33,6 +33,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.IpSecAlgorithm;
@@ -44,6 +45,7 @@
 import android.net.IpSecTunnelInterfaceResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.net.LinkAddress;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.os.Binder;
 import android.os.INetworkManagementService;
@@ -53,6 +55,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.IpSecService.TunnelInterfaceRecord;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -109,6 +113,7 @@
     };
 
     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
+    ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
 
     MockContext mMockContext = new MockContext() {
         @Override
@@ -116,12 +121,22 @@
             switch(name) {
                 case Context.APP_OPS_SERVICE:
                     return mMockAppOps;
+                case Context.CONNECTIVITY_SERVICE:
+                    return mMockConnectivityMgr;
                 default:
                     return null;
             }
         }
 
         @Override
+        public String getSystemServiceName(Class<?> serviceClass) {
+            if (ConnectivityManager.class == serviceClass) {
+                return Context.CONNECTIVITY_SERVICE;
+            }
+            return null;
+        }
+
+        @Override
         public PackageManager getPackageManager() {
             return mMockPkgMgr;
         }
@@ -151,6 +166,10 @@
             new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
     private static final int REMOTE_ENCAP_PORT = 4500;
 
+    private static final String BLESSED_PACKAGE = "blessedPackage";
+    private static final String SYSTEM_PACKAGE = "systemPackage";
+    private static final String BAD_PACKAGE = "badPackage";
+
     public IpSecServiceParameterizedTest(
             String sourceAddr, String destAddr, String localInnerAddr, int family) {
         mSourceAddr = sourceAddr;
@@ -174,15 +193,15 @@
         when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
 
         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
-            .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
         // A system package will not be granted the app op, so this should fall back to
         // a permissions check, which should pass.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage")))
-            .thenReturn(AppOpsManager.MODE_DEFAULT);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
         // A mismatch between the package name and the UID will return MODE_IGNORED.
-        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage")))
-            .thenReturn(AppOpsManager.MODE_IGNORED);
+        when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
     }
 
     //TODO: Add a test to verify SPI.
@@ -338,7 +357,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -352,7 +371,7 @@
         ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
@@ -370,14 +389,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -396,14 +415,14 @@
 
         if (mFamily == AF_INET) {
             IpSecTransformResponse createTransformResp =
-                    mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                    mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
             assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
             verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
         } else {
             try {
                 IpSecTransformResponse createTransformResp =
-                        mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                        mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
             } catch (IllegalArgumentException expected) {
             }
@@ -417,12 +436,12 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         assertEquals(IpSecManager.Status.OK, createTransformResp.status);
 
         // Attempting to create transform a second time with the same SPIs should throw an error...
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -430,7 +449,7 @@
         // ... even if the transform is deleted
         mIpSecService.deleteTransform(createTransformResp.resourceId);
         try {
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+            mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
                 fail("IpSecService should have thrown an error for reuse of SPI");
         } catch (IllegalStateException expected) {
         }
@@ -443,7 +462,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -467,7 +486,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         mIpSecService.deleteTransform(createTransformResp.resourceId);
 
         verify(mMockNetd, times(1))
@@ -515,7 +534,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -562,7 +581,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -592,7 +611,7 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -638,7 +657,7 @@
     @Test
     public void testCreateTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Check that we have stored the tracking object, and retrieve it
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
@@ -661,11 +680,11 @@
     @Test
     public void testDeleteTunnelInterface() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
 
-        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
+        mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE);
 
         // Verify quota and RefcountedResource objects cleaned up
         assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
@@ -678,10 +697,73 @@
         }
     }
 
+    private Network createFakeUnderlyingNetwork(String interfaceName) {
+        final Network fakeNetwork = new Network(1000);
+        final LinkProperties fakeLp = new LinkProperties();
+        fakeLp.setInterfaceName(interfaceName);
+        when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp);
+        return fakeNetwork;
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterface() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface");
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        mIpSecService.setNetworkForTunnelInterface(
+                tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
+
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+        assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork());
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final Network newFakeNetwork = new Network(1000);
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE);
+            fail("Expected an IllegalArgumentException for invalid resource ID.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception {
+        final IpSecTunnelInterfaceResponse createTunnelResp =
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+        final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+        final IpSecService.UserRecord userRecord =
+                mIpSecService.mUserResourceTracker.getUserRecord(mUid);
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId);
+
+        final Network newFakeNetwork =
+                createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName());
+
+        try {
+            mIpSecService.setNetworkForTunnelInterface(
+                    tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+            fail(
+                    "Expected an IllegalArgumentException because the underlying network is the"
+                            + " network being exposed by this tunnel.");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
     @Test
     public void testTunnelInterfaceBinderDeath() throws Exception {
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
         IpSecService.RefcountedResource refcountedRecord =
@@ -718,9 +800,9 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         if (closeSpiBeforeApply) {
             mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
@@ -728,8 +810,8 @@
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -758,17 +840,17 @@
         addAuthAndCryptToIpSecConfig(ipSecConfig);
 
         IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+                mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE);
         IpSecTunnelInterfaceResponse createTunnelResp =
-                createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+                createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
 
         // Close SPI record
         mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
-        mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
-                transformResourceId, "blessedPackage");
+        mIpSecService.applyTunnelModeTransform(
+                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
 
         for (int selAddrFamily : ADDRESS_FAMILIES) {
             verify(mMockNetd)
@@ -790,7 +872,7 @@
 
     @Test
     public void testAddRemoveAddressFromTunnelInterface() throws Exception {
-        for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
+        for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) {
             IpSecTunnelInterfaceResponse createTunnelResp =
                     createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName);
             mIpSecService.addAddressToTunnelInterface(
@@ -816,7 +898,7 @@
     public void testAddTunnelFailsForBadPackageName() throws Exception {
         try {
             IpSecTunnelInterfaceResponse createTunnelResp =
-                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage");
+                    createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE);
             fail("Expected a SecurityException for badPackage.");
         } catch (SecurityException expected) {
         }
@@ -830,7 +912,7 @@
         try {
             String addr = Inet4Address.getLoopbackAddress().getHostAddress();
             mIpSecService.createTunnelInterface(
-                    addr, addr, new Network(0), new Binder(), "blessedPackage");
+                    addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE);
             fail("Expected UnsupportedOperationException for disabled feature");
         } catch (UnsupportedOperationException expected) {
         }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index cd4cfcf..46c4081 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -67,6 +67,8 @@
 @SmallTest
 public class NetworkNotificationManagerTest {
 
+    private static final String TEST_SSID = "Test SSID";
+    private static final String TEST_EXTRA_INFO = "extra";
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -76,6 +78,7 @@
 
         WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        WIFI_CAPABILITIES.setSSID(TEST_SSID);
 
         // Set the underyling network to wifi.
         VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
@@ -129,7 +132,7 @@
         when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
         when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
-        when(mNetworkInfo.getExtraInfo()).thenReturn("extra");
+        when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
         when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
 
@@ -143,11 +146,11 @@
                 .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any());
         final int transportType = NetworkNotificationManager.approximateTransportType(nai);
         if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
-            verify(mResources, times(1)).getString(title, eq(any()));
+            verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO));
         } else {
             verify(mResources, times(1)).getString(title);
         }
-        verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed);
+        verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 73cc9f1..cffd2d1d 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -74,6 +75,7 @@
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.os.Build.VERSION_CODES;
@@ -984,6 +986,13 @@
         startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
     }
 
+    private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
+        assertNotNull(nc);
+        VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
+        assertNotNull(ti);
+        assertEquals(type, ti.type);
+    }
+
     public void startRacoon(final String serverAddr, final String expectedAddr)
             throws Exception {
         final ConditionVariable legacyRunnerReady = new ConditionVariable();
@@ -1020,8 +1029,10 @@
 
             // Now wait for the runner to be ready before testing for the route.
             ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+            ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
             verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
-                    lpCaptor.capture(), any(), anyInt(), any(), anyInt());
+                    lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt());
 
             // In this test the expected address is always v4 so /32.
             // Note that the interface needs to be specified because RouteInfo objects stored in
@@ -1031,6 +1042,8 @@
             final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
             assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
                     actualRoutes.contains(expectedRoute));
+
+            assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
         } finally {
             // Now interrupt the thread, unblock the runner and clean up.
             vpn.mVpnRunner.exitVpnRunner();
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
similarity index 64%
rename from tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
rename to tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index d1d6a26..0f920b3 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -16,8 +16,11 @@
 
 package com.android.framework.permission.tests;
 
+import android.content.Context;
 import android.os.Binder;
-import android.os.IVibratorService;
+import android.os.CombinedVibrationEffect;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -32,27 +35,28 @@
  * Verify that Hardware apis cannot be called without required permissions.
  */
 @SmallTest
-public class VibratorServicePermissionTest extends TestCase {
+public class VibratorManagerServicePermissionTest extends TestCase {
 
-    private IVibratorService mVibratorService;
+    private IVibratorManagerService mVibratorService;
 
     @Override
     protected void setUp() throws Exception {
-        mVibratorService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mVibratorService = IVibratorManagerService.Stub.asInterface(
+                ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String,
+     * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testVibrate() throws RemoteException {
         try {
-            final VibrationEffect effect =
-                    VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
-            final VibrationAttributes attrs = new VibrationAttributes.Builder()
+            CombinedVibrationEffect effect =
+                    CombinedVibrationEffect.createSynced(
+                            VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
+            VibrationAttributes attrs = new VibrationAttributes.Builder()
                     .setUsage(VibrationAttributes.USAGE_ALARM)
                     .build();
             mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
@@ -64,10 +68,10 @@
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
+     * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires
+     * permissions.
      * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
+     * {@link android.Manifest.permission#VIBRATE}
      */
     public void testCancelVibrate() throws RemoteException {
         try {
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 1102624..4a1f96d 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -42,6 +42,8 @@
 
     private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>();
 
+    private boolean mUseRegisteredHandlers;
+
     public abstract class FutureIntent extends FutureTask<Intent> {
         public FutureIntent() {
             super(
@@ -61,17 +63,24 @@
     public class BroadcastInterceptor extends FutureIntent {
         private final BroadcastReceiver mReceiver;
         private final IntentFilter mFilter;
+        private final Handler mHandler;
 
-        public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) {
+        public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter,
+                Handler handler) {
             mReceiver = receiver;
             mFilter = filter;
+            mHandler = mUseRegisteredHandlers ? handler : null;
         }
 
         public boolean dispatchBroadcast(Intent intent) {
             if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) {
                 if (mReceiver != null) {
                     final Context context = BroadcastInterceptingContext.this;
-                    mReceiver.onReceive(context, intent);
+                    if (mHandler == null) {
+                        mReceiver.onReceive(context, intent);
+                    } else {
+                        mHandler.post(() -> mReceiver.onReceive(context, intent));
+                    }
                     return false;
                 } else {
                     set(intent);
@@ -116,25 +125,38 @@
     }
 
     public FutureIntent nextBroadcastIntent(IntentFilter filter) {
-        final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter);
+        final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null);
         synchronized (mInterceptors) {
             mInterceptors.add(interceptor);
         }
         return interceptor;
     }
 
+    /**
+     * Whether to send broadcasts to registered handlers. By default, receivers are called
+     * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is
+     * instead called by a runnable posted to the Handler specified when the receiver was
+     * registered. This method applies only to future registrations, already-registered receivers
+     * are unaffected.
+     */
+    public void setUseRegisteredHandlers(boolean use) {
+        synchronized (mInterceptors) {
+            mUseRegisteredHandlers = use;
+        }
+    }
+
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        synchronized (mInterceptors) {
-            mInterceptors.add(new BroadcastInterceptor(receiver, filter));
-        }
-        return null;
+        return registerReceiver(receiver, filter, null, null);
     }
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
             String broadcastPermission, Handler scheduler) {
-        return registerReceiver(receiver, filter);
+        synchronized (mInterceptors) {
+            mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler));
+        }
+        return null;
     }
 
     @Override
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index f9db408..7dada9d 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -65,7 +65,7 @@
                 ArgumentCaptor.forClass(IVcnUnderlyingNetworkPolicyListener.class);
         verify(mMockVcnManagementService).addVcnUnderlyingNetworkPolicyListener(captor.capture());
 
-        assertTrue(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertTrue(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
 
         IVcnUnderlyingNetworkPolicyListener listenerWrapper = captor.getValue();
         listenerWrapper.onPolicyChanged();
@@ -78,7 +78,7 @@
 
         mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
         verify(mMockVcnManagementService)
                 .addVcnUnderlyingNetworkPolicyListener(
                         any(IVcnUnderlyingNetworkPolicyListener.class));
@@ -88,7 +88,7 @@
     public void testRemoveVcnUnderlyingNetworkPolicyListenerUnknownListener() throws Exception {
         mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener));
+        assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener));
         verify(mMockVcnManagementService, never())
                 .addVcnUnderlyingNetworkPolicyListener(
                         any(IVcnUnderlyingNetworkPolicyListener.class));
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 4859644..c290bff 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -16,6 +16,10 @@
 
 package com.android.server;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
 import static com.android.server.vcn.VcnTestUtils.setupSystemService;
@@ -106,6 +110,7 @@
             Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG));
 
     private static final int TEST_SUBSCRIPTION_ID = 1;
+    private static final int TEST_SUBSCRIPTION_ID_2 = 2;
     private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
             new SubscriptionInfo(
                     TEST_SUBSCRIPTION_ID /* id */,
@@ -537,59 +542,121 @@
                 Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
     }
 
-    private void verifyMergedNetworkCapabilitiesIsVcnManaged(
-            NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+    private void verifyMergedNetworkCapabilities(
+            NetworkCapabilities mergedCapabilities,
+            @Transport int transportType,
+            boolean isVcnManaged,
+            boolean isRestricted) {
         assertTrue(mergedCapabilities.hasTransport(transportType));
-        assertFalse(
+        assertEquals(
+                !isVcnManaged,
                 mergedCapabilities.hasCapability(
                         NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+        assertEquals(
+                !isRestricted,
+                mergedCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+    private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) {
+        setUpVcnSubscription(subId, subGrp);
+        final Vcn vcn = startAndGetVcnInstance(subGrp);
+        doReturn(isVcnActive).when(vcn).isActive();
+    }
+
+    private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport(
+            int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) {
+        setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
+
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder();
+        ncBuilder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        if (transport == TRANSPORT_CELLULAR) {
+            ncBuilder
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID));
+        } else if (transport == TRANSPORT_WIFI) {
+            WifiInfo wifiInfo = mock(WifiInfo.class);
+            when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+            when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+
+            ncBuilder
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .setTransportInfo(wifiInfo);
+        } else {
+            throw new IllegalArgumentException("Unknown transport");
+        }
+
+        return mVcnMgmtSvc.getUnderlyingNetworkPolicy(ncBuilder.build(), new LinkProperties());
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
-        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
-        NetworkCapabilities nc =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
-                        .build();
-
-        VcnUnderlyingNetworkPolicy policy =
-                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR);
 
         assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilitiesIsVcnManaged(
-                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                TRANSPORT_CELLULAR,
+                true /* isVcnManaged */,
+                false /* isRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception {
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID,
+                        TEST_UUID_2,
+                        false /* isActive */,
+                        TRANSPORT_CELLULAR);
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_CELLULAR,
+                false /* isVcnManaged */,
+                false /* isRestricted */);
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
-        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
-
-        WifiInfo wifiInfo = mock(WifiInfo.class);
-        when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
-        when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
-        NetworkCapabilities nc =
-                new NetworkCapabilities.Builder()
-                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                        .setTransportInfo(wifiInfo)
-                        .build();
-
-        VcnUnderlyingNetworkPolicy policy =
-                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI);
 
         assertFalse(policy.isTeardownRequested());
-        verifyMergedNetworkCapabilitiesIsVcnManaged(
-                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_WIFI,
+                true /* isVcnManaged */,
+                true /* isRestricted */);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception {
+        final VcnUnderlyingNetworkPolicy policy =
+                startVcnAndGetPolicyForTransport(
+                        TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI);
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilities(
+                policy.getMergedNetworkCapabilities(),
+                NetworkCapabilities.TRANSPORT_WIFI,
+                false /* isVcnManaged */,
+                true /* isRestricted */);
     }
 
     @Test
     public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+        setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */);
+
         NetworkCapabilities nc =
                 new NetworkCapabilities.Builder()
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
                         .build();
 
         VcnUnderlyingNetworkPolicy policy =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 278d93a..a6eae96 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -34,8 +34,10 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.net.LinkProperties;
+import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 
 import androidx.test.filters.SmallTest;
@@ -73,6 +75,11 @@
     }
 
     @Test
+    public void testEnterStateDoesNotCancelSafemodeAlarm() {
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    }
+
+    @Test
     public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
         mGatewayConnection
                 .getUnderlyingNetworkTrackerCallback()
@@ -81,6 +88,7 @@
 
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
         verify(mIkeSession, never()).close();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -121,6 +129,9 @@
 
     @Test
     public void testChildOpenedRegistersNetwork() throws Exception {
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
         final VcnChildSessionConfiguration mMockChildSessionConfig =
                 mock(VcnChildSessionConfiguration.class);
         doReturn(Collections.singletonList(TEST_INTERNAL_ADDR))
@@ -162,22 +173,41 @@
         for (int cap : mConfig.getAllExposedCapabilities()) {
             assertTrue(nc.hasCapability(cap));
         }
+
+        // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
+        // canceled
+        mGatewayConnection.mNetworkAgent.onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
+        verify(mSafemodeTimeoutAlarm).cancel();
     }
 
     @Test
     public void testChildSessionClosedTriggersDisconnect() throws Exception {
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
         getChildSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
     }
 
     @Test
     public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+        // Verify scheduled but not canceled when entering ConnectedState
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+
+        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
+        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
index d936183..07282c9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -61,6 +61,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).kill();
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -73,6 +74,7 @@
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
         verify(mIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -92,6 +94,7 @@
 
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -101,5 +104,11 @@
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
         verify(mIkeSession).close();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
index 8643d8a..49ce54d 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -16,12 +16,18 @@
 
 package com.android.server.vcn;
 
+import static android.net.IpSecManager.IpSecTunnelInterface;
+
+import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
+import android.net.IpSecManager;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -37,7 +43,13 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
+        final IpSecTunnelInterface tunnelIface =
+                mContext.getSystemService(IpSecManager.class)
+                        .createIpSecTunnelInterface(
+                                DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
+        mGatewayConnection.setTunnelInterface(tunnelIface);
+
+        // Don't need to transition to DisconnectedState because it is the starting state
         mTestLooper.dispatchAll();
     }
 
@@ -67,6 +79,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -77,6 +90,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
     }
 
     @Test
@@ -86,5 +100,6 @@
 
         assertNull(mGatewayConnection.getCurrentState());
         verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
+        verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
index d0fec55..22eab2a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.vcn;
 
-import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import androidx.test.filters.SmallTest;
@@ -28,8 +28,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.concurrent.TimeUnit;
-
 /** Tests for VcnGatewayConnection.DisconnectedState */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -40,6 +38,9 @@
 
         mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
 
+        // ensure that mGatewayConnection has an underlying Network before entering
+        // DisconnectingState
+        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_2);
         mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
         mTestLooper.dispatchAll();
     }
@@ -49,12 +50,22 @@
         getIkeSessionCallback().onClosed();
         mTestLooper.dispatchAll();
 
-        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verify(mMockIkeSession).close();
+        verify(mMockIkeSession, never()).kill();
+        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutExpired() throws Exception {
-        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        Runnable delayedEvent =
+                verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyTeardownTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         verify(mMockIkeSession).kill();
@@ -67,5 +78,11 @@
 
         // Should do nothing; already tearing down.
         assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
index 3f2b47c..6c26075 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java
@@ -29,10 +29,14 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnectionTestBase {
+    private long mFirstRetryInterval;
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
+        mFirstRetryInterval = mConfig.getRetryInterval()[0];
+
         mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
         mGatewayConnection.transitionTo(mGatewayConnection.mRetryTimeoutState);
         mTestLooper.dispatchAll();
@@ -46,6 +50,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
@@ -56,6 +61,7 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, false /* expectCanceled */);
     }
 
     @Test
@@ -66,13 +72,28 @@
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
     }
 
     @Test
     public void testTimeoutElapsingTriggersRetry() throws Exception {
-        mTestLooper.moveTimeForward(mConfig.getRetryIntervalsMs()[0]);
+        final Runnable delayedEvent =
+                verifyRetryTimeoutAlarmAndGetCallback(
+                        mFirstRetryInterval, false /* expectCanceled */);
+
+        // Can't use mTestLooper to advance the time since VcnGatewayConnection uses WakeupMessages
+        // (which are mocked here). Directly invoke the runnable instead. This is still sufficient,
+        // since verifyRetryTimeoutAlarmAndGetCallback() verifies the WakeupMessage was scheduled
+        // with the correct delay.
+        delayedEvent.run();
         mTestLooper.dispatchAll();
 
         assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+        verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
+    }
+
+    @Test
+    public void testSafemodeTimeoutNotifiesCallback() {
+        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index bc6bee2..748c792 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -132,10 +132,32 @@
 
     @Test
     public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+        verifyWakeLockSetUp();
+
         final TelephonySubscriptionSnapshot updatedSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
         mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
 
         verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
+        verifyWakeLockAcquired();
+
+        mTestLooper.dispatchAll();
+
+        verifyWakeLockReleased();
+    }
+
+    @Test
+    public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+
+        verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
+
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+        verify(mDisconnectRequestAlarm).cancel();
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index d449eab..ac9ec06 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -20,10 +20,16 @@
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -42,12 +48,16 @@
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
+import android.os.PowerManager;
 import android.os.test.TestLooper;
 
+import com.android.internal.util.State;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.IpSecService;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
 
 import org.junit.Before;
 import org.mockito.ArgumentCaptor;
@@ -55,6 +65,7 @@
 import java.net.InetAddress;
 import java.util.Collections;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 public class VcnGatewayConnectionTestBase {
     protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
@@ -68,6 +79,7 @@
     protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
     protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
     protected static final int TEST_SUB_ID = 5;
+    protected static final long ELAPSED_REAL_TIME = 123456789L;
     protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
     protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
             new UnderlyingNetworkRecord(
@@ -94,6 +106,11 @@
     @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
     @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+    @NonNull protected final VcnWakeLock mWakeLock;
+    @NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
+    @NonNull protected final WakeupMessage mDisconnectRequestAlarm;
+    @NonNull protected final WakeupMessage mRetryTimeoutAlarm;
+    @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
 
     @NonNull protected final IpSecService mIpSecSvc;
     @NonNull protected final ConnectivityManager mConnMgr;
@@ -110,6 +127,11 @@
         mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
         mDeps = mock(VcnGatewayConnection.Dependencies.class);
         mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+        mWakeLock = mock(VcnWakeLock.class);
+        mTeardownTimeoutAlarm = mock(WakeupMessage.class);
+        mDisconnectRequestAlarm = mock(WakeupMessage.class);
+        mRetryTimeoutAlarm = mock(WakeupMessage.class);
+        mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
 
         mIpSecSvc = mock(IpSecService.class);
         setupIpSecManager(mContext, mIpSecSvc);
@@ -125,6 +147,20 @@
         doReturn(mUnderlyingNetworkTracker)
                 .when(mDeps)
                 .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
+        doReturn(mWakeLock)
+                .when(mDeps)
+                .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+
+        setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
+        setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
+        setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
+        setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
+
+        doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
+    }
+
+    private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
+        doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
     }
 
     @Before
@@ -166,4 +202,80 @@
         verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
         return (VcnChildSessionCallback) captor.getValue();
     }
+
+    protected void verifyWakeLockSetUp() {
+        verify(mDeps).newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockAcquired() {
+        verify(mWakeLock).acquire();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    protected void verifyWakeLockReleased() {
+        verify(mWakeLock).release();
+        verifyNoMoreInteractions(mWakeLock);
+    }
+
+    private Runnable verifyWakeupMessageSetUpAndGetCallback(
+            @NonNull String tag,
+            @NonNull WakeupMessage msg,
+            long delayInMillis,
+            boolean expectCanceled) {
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(tag), runnableCaptor.capture());
+
+        verify(mDeps, atLeastOnce()).getElapsedRealTime();
+        verify(msg).schedule(ELAPSED_REAL_TIME + delayInMillis);
+        verify(msg, expectCanceled ? times(1) : never()).cancel();
+
+        return runnableCaptor.getValue();
+    }
+
+    protected Runnable verifyTeardownTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM,
+                mTeardownTimeoutAlarm,
+                TimeUnit.SECONDS.toMillis(VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyDisconnectRequestAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.DISCONNECT_REQUEST_ALARM,
+                mDisconnectRequestAlarm,
+                TimeUnit.SECONDS.toMillis(
+                        VcnGatewayConnection.NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected Runnable verifyRetryTimeoutAlarmAndGetCallback(
+            long delayInMillis, boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.RETRY_TIMEOUT_ALARM,
+                mRetryTimeoutAlarm,
+                delayInMillis,
+                expectCanceled);
+    }
+
+    protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
+        return verifyWakeupMessageSetUpAndGetCallback(
+                VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
+                mSafemodeTimeoutAlarm,
+                TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
+                expectCanceled);
+    }
+
+    protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
+        // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
+        // state)
+        final Runnable delayedEvent =
+                verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
+        delayedEvent.run();
+        mTestLooper.dispatchAll();
+
+        verify(mGatewayStatusCallback).onEnteredSafemode();
+        assertEquals(expectedState, mGatewayConnection.getCurrentState());
+    }
 }